python-packaging

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Python Packaging

Python 包打包指南

Comprehensive guide to creating, structuring, and distributing Python packages using modern packaging tools, pyproject.toml, and publishing to PyPI.
本指南全面介绍如何使用现代打包工具、pyproject.toml创建、构建结构并分发Python包,以及发布至PyPI的流程。

When to Use This Skill

何时使用本技能

  • Creating Python libraries for distribution
  • Building command-line tools with entry points
  • Publishing packages to PyPI or private repositories
  • Setting up Python project structure
  • Creating installable packages with dependencies
  • Building wheels and source distributions
  • Versioning and releasing Python packages
  • Creating namespace packages
  • Implementing package metadata and classifiers
  • 创建用于分发的Python库
  • 构建带有入口点的命令行工具
  • 将包发布至PyPI或私有仓库
  • 搭建Python项目结构
  • 创建包含依赖的可安装包
  • 构建wheel和源码分发包
  • Python包的版本管理与发布
  • 创建命名空间包
  • 配置包元数据与分类器

Core Concepts

核心概念

1. Package Structure

1. 包结构

  • Source layout:
    src/package_name/
    (recommended)
  • Flat layout:
    package_name/
    (simpler but less flexible)
  • Package metadata: pyproject.toml, setup.py, or setup.cfg
  • Distribution formats: wheel (.whl) and source distribution (.tar.gz)
  • 源码布局
    src/package_name/
    (推荐)
  • 扁平布局
    package_name/
    (更简单但灵活性较低)
  • 包元数据:pyproject.toml、setup.py 或 setup.cfg
  • 分发格式:wheel(.whl)和源码分发包(.tar.gz)

2. Modern Packaging Standards

2. 现代打包标准

  • PEP 517/518: Build system requirements
  • PEP 621: Metadata in pyproject.toml
  • PEP 660: Editable installs
  • pyproject.toml: Single source of configuration
  • PEP 517/518:构建系统要求
  • PEP 621:pyproject.toml中的元数据规范
  • PEP 660:可编辑安装
  • pyproject.toml:单一配置源

3. Build Backends

3. 构建后端

  • setuptools: Traditional, widely used
  • hatchling: Modern, opinionated
  • flit: Lightweight, for pure Python
  • poetry: Dependency management + packaging
  • setuptools:传统且广泛使用的工具
  • hatchling:现代、约定式的构建工具
  • flit:轻量级工具,适用于纯Python项目
  • poetry:集依赖管理与打包于一体的工具

4. Distribution

4. 分发渠道

  • PyPI: Python Package Index (public)
  • TestPyPI: Testing before production
  • Private repositories: JFrog, AWS CodeArtifact, etc.
  • PyPI:Python包索引(公开)
  • TestPyPI:生产环境发布前的测试环境
  • 私有仓库:JFrog、AWS CodeArtifact等

Quick Start

快速开始

Minimal Package Structure

最小化包结构

my-package/
├── pyproject.toml
├── README.md
├── LICENSE
├── src/
│   └── my_package/
│       ├── __init__.py
│       └── module.py
└── tests/
    └── test_module.py
my-package/
├── pyproject.toml
├── README.md
├── LICENSE
├── src/
│   └── my_package/
│       ├── __init__.py
│       └── module.py
└── tests/
    └── test_module.py

Minimal pyproject.toml

最小化pyproject.toml配置

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

[project]
name = "my-package"
version = "0.1.0"
description = "A short description"
authors = [{name = "Your Name", email = "you@example.com"}]
readme = "README.md"
requires-python = ">=3.8"
dependencies = [
    "requests>=2.28.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.0",
    "black>=22.0",
]
toml
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

[project]
name = "my-package"
version = "0.1.0"
description = "A short description"
authors = [{name = "Your Name", email = "you@example.com"}]
readme = "README.md"
requires-python = ">=3.8"
dependencies = [
    "requests>=2.28.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.0",
    "black>=22.0",
]

Package Structure Patterns

包结构模式

Pattern 1: Source Layout (Recommended)

模式1:源码布局(推荐)

my-package/
├── pyproject.toml
├── README.md
├── LICENSE
├── .gitignore
├── src/
│   └── my_package/
│       ├── __init__.py
│       ├── core.py
│       ├── utils.py
│       └── py.typed          # For type hints
├── tests/
│   ├── __init__.py
│   ├── test_core.py
│   └── test_utils.py
└── docs/
    └── index.md
Advantages:
  • Prevents accidentally importing from source
  • Cleaner test imports
  • Better isolation
pyproject.toml for source layout:
toml
[tool.setuptools.packages.find]
where = ["src"]
my-package/
├── pyproject.toml
├── README.md
├── LICENSE
├── .gitignore
├── src/
│   └── my_package/
│       ├── __init__.py
│       ├── core.py
│       ├── utils.py
│       └── py.typed          # 用于类型提示
├── tests/
│   ├── __init__.py
│   ├── test_core.py
│   └── test_utils.py
└── docs/
    └── index.md
优势:
  • 避免意外导入源码目录
  • 更清晰的测试导入路径
  • 更好的隔离性
源码布局对应的pyproject.toml配置:
toml
[tool.setuptools.packages.find]
where = ["src"]

Pattern 2: Flat Layout

模式2:扁平布局

my-package/
├── pyproject.toml
├── README.md
├── my_package/
│   ├── __init__.py
│   └── module.py
└── tests/
    └── test_module.py
Simpler but:
  • Can import package without installing
  • Less professional for libraries
my-package/
├── pyproject.toml
├── README.md
├── my_package/
│   ├── __init__.py
│   └── module.py
└── tests/
    └── test_module.py
更简单但存在以下问题:
  • 无需安装即可导入包
  • 对于库来说不够专业

Pattern 3: Multi-Package Project

模式3:多包项目

project/
├── pyproject.toml
├── packages/
│   ├── package-a/
│   │   └── src/
│   │       └── package_a/
│   └── package-b/
│       └── src/
│           └── package_b/
└── tests/
project/
├── pyproject.toml
├── packages/
│   ├── package-a/
│   │   └── src/
│   │       └── package_a/
│   └── package-b/
│       └── src/
│           └── package_b/
└── tests/

Complete pyproject.toml Examples

完整pyproject.toml示例

Pattern 4: Full-Featured pyproject.toml

模式4:全功能pyproject.toml

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

[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",
]

[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]",
]

[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"

[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"]
toml
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[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",
]

[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]",
]

[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"

[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"]

Black configuration

Black配置

[tool.black] line-length = 100 target-version = ["py38", "py39", "py310", "py311"] include = '.pyi?$'
[tool.black] line-length = 100 target-version = ["py38", "py39", "py310", "py311"] include = '.pyi?$'

Ruff configuration

Ruff配置

[tool.ruff] line-length = 100 target-version = "py38"
[tool.ruff.lint] select = ["E", "F", "I", "N", "W", "UP"]
[tool.ruff] line-length = 100 target-version = "py38"
[tool.ruff.lint] select = ["E", "F", "I", "N", "W", "UP"]

MyPy configuration

MyPy配置

[tool.mypy] python_version = "3.8" warn_return_any = true warn_unused_configs = true disallow_untyped_defs = true
[tool.mypy] python_version = "3.8" warn_return_any = true warn_unused_configs = true disallow_untyped_defs = true

Pytest configuration

Pytest配置

[tool.pytest.ini_options] testpaths = ["tests"] python_files = ["test_*.py"] addopts = "-v --cov=my_package --cov-report=term-missing"
[tool.pytest.ini_options] testpaths = ["tests"] python_files = ["test_*.py"] addopts = "-v --cov=my_package --cov-report=term-missing"

Coverage configuration

Coverage配置

[tool.coverage.run] source = ["src"] omit = ["/tests/"]
[tool.coverage.report] exclude_lines = [ "pragma: no cover", "def repr", "raise AssertionError", "raise NotImplementedError", ]
undefined
[tool.coverage.run] source = ["src"] omit = ["/tests/"]
[tool.coverage.report] exclude_lines = [ "pragma: no cover", "def repr", "raise AssertionError", "raise NotImplementedError", ]
undefined

Pattern 5: Dynamic Versioning

模式5:动态版本管理

toml
[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__"}
toml
[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

或使用setuptools-scm基于Git进行版本管理

[tool.setuptools_scm] write_to = "src/my_package/_version.py"

**In **init**.py:**

```python
[tool.setuptools_scm] write_to = "src/my_package/_version.py"

**在__init__.py中配置:**

```python

src/my_package/init.py

src/my_package/init.py

version = "1.0.0"
version = "1.0.0"

Or with setuptools-scm

或使用setuptools-scm

from importlib.metadata import version version = version("my-package")
undefined
from importlib.metadata import version version = version("my-package")
undefined

Command-Line Interface (CLI) Patterns

命令行界面(CLI)模式

Pattern 6: CLI with Click

模式6:基于Click的CLI

python
undefined
python
undefined

src/my_package/cli.py

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()

**Register in pyproject.toml:**

```toml
[project.scripts]
my-tool = "my_package.cli:main"
Usage:
bash
pip install -e .
my-tool greet World
my-tool greet Alice --greeting="Hi"
my-tool repeat --count=3
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(): """CLI入口点。""" cli()
if name == "main": main()

**在pyproject.toml中注册:**

```toml
[project.scripts]
my-tool = "my_package.cli:main"
使用方式:
bash
pip install -e .
my-tool greet World
my-tool greet Alice --greeting="Hi"
my-tool repeat --count=3

Pattern 7: CLI with argparse

模式7:基于argparse的CLI

python
undefined
python
undefined

src/my_package/cli.py

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()
undefined
import argparse import sys
def main(): """CLI主入口点。""" 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")

# 添加子命令
process_parser = subparsers.add_parser("process", help="处理数据")
process_parser.add_argument("input_file", help="输入文件路径")
process_parser.add_argument(
    "--output", "-o",
    default="output.txt",
    help="输出文件路径"
)

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): """将数据从输入文件处理至输出文件。""" print(f"Processing {input_file} -> {output_file}")
if name == "main": main()
undefined

Building and Publishing

构建与发布

Pattern 8: Build Package Locally

模式8:本地构建包

bash
undefined
bash
undefined

Install build tools

安装构建工具

pip install build twine
pip install build twine

Build distribution

构建分发包

python -m build
python -m build

This creates:

执行后会生成:

dist/

dist/

my-package-1.0.0.tar.gz (source distribution)

my-package-1.0.0.tar.gz (源码分发包)

my_package-1.0.0-py3-none-any.whl (wheel)

my_package-1.0.0-py3-none-any.whl (wheel包)

Check the distribution

检查分发包

twine check dist/*
undefined
wine check dist/*
undefined

Pattern 9: Publishing to PyPI

模式9:发布至PyPI

bash
undefined
bash
undefined

Install publishing tools

安装发布工具

pip install twine
pip install twine

Test on TestPyPI first

先在TestPyPI测试

twine upload --repository testpypi dist/*
wine upload --repository testpypi dist/*

Install from TestPyPI to test

从TestPyPI安装进行测试

pip install --index-url https://test.pypi.org/simple/ my-package
pip install --index-url https://test.pypi.org/simple/ my-package

If all good, publish to PyPI

测试通过后,发布至PyPI

twine upload dist/*

**Using API tokens (recommended):**

```bash
wine upload dist/*

**推荐使用API令牌:**

```bash

Create ~/.pypirc

创建~/.pypirc文件

[distutils] index-servers = pypi testpypi
[pypi] username = token password = pypi-...your-token...
[testpypi] username = token password = pypi-...your-test-token...
undefined
[distutils] index-servers = pypi testpypi
[pypi] username = token password = pypi-...your-token...
[testpypi] username = token password = pypi-...your-test-token...
undefined

Pattern 10: Automated Publishing with GitHub Actions

模式10:使用GitHub Actions自动化发布

yaml
undefined
yaml
undefined

.github/workflows/publish.yml

.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/*
undefined
name: 发布至PyPI
on: release: types: [created]
jobs: publish: runs-on: ubuntu-latest
steps:
  - uses: actions/checkout@v3

  - name: 设置Python环境
    uses: actions/setup-python@v4
    with:
      python-version: "3.11"

  - name: 安装依赖
    run: |
      pip install build twine

  - name: 构建包
    run: python -m build

  - name: 检查包
    run: twine check dist/*

  - name: 发布至PyPI
    env:
      TWINE_USERNAME: __token__
      TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
    run: twine upload dist/*
undefined

Advanced Patterns

高级模式

Pattern 11: Including Data Files

模式11:包含数据文件

toml
[tool.setuptools.package-data]
my_package = [
    "data/*.json",
    "templates/*.html",
    "static/css/*.css",
    "py.typed",
]
Accessing data files:
python
undefined
toml
[tool.setuptools.package-data]
my_package = [
    "data/*.json",
    "templates/*.html",
    "static/css/*.css",
    "py.typed",
]
访问数据文件:
python
undefined

src/my_package/loader.py

src/my_package/loader.py

from importlib.resources import files import json
def load_config(): """Load configuration from package data.""" config_file = files("my_package").joinpath("data/config.json") with config_file.open() as f: return json.load(f)
from importlib.resources import files import json
def load_config(): """从包数据中加载配置。""" config_file = files("my_package").joinpath("data/config.json") with config_file.open() as f: return json.load(f)

Python 3.9+

Python 3.9+

from importlib.resources import files
data = files("my_package").joinpath("data/file.txt").read_text()
undefined
from importlib.resources import files
data = files("my_package").joinpath("data/file.txt").read_text()
undefined

Pattern 12: Namespace Packages

模式12:命名空间包

For large projects split across multiple repositories:
undefined
适用于拆分为多个仓库的大型项目:
undefined

Package 1: company-core

包1:company-core

company/ └── core/ ├── init.py └── models.py
company/ └── core/ ├── init.py └── models.py

Package 2: company-api

包2:company-api

company/ └── api/ ├── init.py └── routes.py

**Do NOT include **init**.py in the namespace directory (company/):**

```toml
company/ └── api/ ├── init.py └── routes.py

**注意:命名空间目录(company/)中不要包含__init__.py文件:**

```toml

company-core/pyproject.toml

company-core/pyproject.toml

[project] name = "company-core"
[tool.setuptools.packages.find] where = ["."] include = ["company.core*"]
[project] name = "company-core"
[tool.setuptools.packages.find] where = ["."] include = ["company.core*"]

company-api/pyproject.toml

company-api/pyproject.toml

[project] name = "company-api"
[tool.setuptools.packages.find] where = ["."] include = ["company.api*"]

**Usage:**

```python
[project] name = "company-api"
[tool.setuptools.packages.find] where = ["."] include = ["company.api*"]

**使用方式:**

```python

Both packages can be imported under same namespace

两个包可在同一命名空间下导入

from company.core import models from company.api import routes
undefined
from company.core import models from company.api import routes
undefined

Pattern 13: C Extensions

模式13:C扩展

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

[tool.setuptools]
ext-modules = [
    {name = "my_package.fast_module", sources = ["src/fast_module.c"]},
]
Or with setup.py:
python
undefined
toml
[build-system]
requires = ["setuptools>=61.0", "wheel", "Cython>=0.29"]
build-backend = "setuptools.build_meta"

[tool.setuptools]
ext-modules = [
    {name = "my_package.fast_module", sources = ["src/fast_module.c"]},
]
或使用setup.py配置:
python
undefined

setup.py

setup.py

from setuptools import setup, Extension
setup( ext_modules=[ Extension( "my_package.fast_module", sources=["src/fast_module.c"], include_dirs=["src/include"], ) ] )
undefined
from setuptools import setup, Extension
setup( ext_modules=[ Extension( "my_package.fast_module", sources=["src/fast_module.c"], include_dirs=["src/include"], ) ] )
undefined

Version Management

版本管理

Pattern 14: Semantic Versioning

模式14:语义化版本

python
undefined
python
undefined

src/my_package/init.py

src/my_package/init.py

version = "1.2.3"
version = "1.2.3"

Semantic versioning: MAJOR.MINOR.PATCH

语义化版本格式:MAJOR.MINOR.PATCH

MAJOR: Breaking changes

MAJOR:不兼容的API变更

MINOR: New features (backward compatible)

MINOR:新增向后兼容的功能

PATCH: Bug fixes

PATCH:向后兼容的问题修复


**Version constraints in dependencies:**

```toml
dependencies = [
    "requests>=2.28.0,<3.0.0",  # Compatible range
    "click~=8.1.0",              # Compatible release (~= 8.1.0 means >=8.1.0,<8.2.0)
    "pydantic>=2.0",             # Minimum version
    "numpy==1.24.3",             # Exact version (avoid if possible)
]

**依赖中的版本约束:**

```toml
dependencies = [
    "requests>=2.28.0,<3.0.0",  # 兼容版本范围
    "click~=8.1.0",              # 兼容发布版本(~=8.1.0 表示 >=8.1.0,<8.2.0)
    "pydantic>=2.0",             # 最低版本要求
    "numpy==1.24.3",             # 精确版本(尽量避免使用)
]

Pattern 15: Git-Based Versioning

模式15:基于Git的版本管理

toml
[build-system]
requires = ["setuptools>=61.0", "setuptools-scm>=8.0"]
build-backend = "setuptools.build_meta"

[project]
name = "my-package"
dynamic = ["version"]

[tool.setuptools_scm]
write_to = "src/my_package/_version.py"
version_scheme = "post-release"
local_scheme = "dirty-tag"
Creates versions like:
  • 1.0.0
    (from git tag)
  • 1.0.1.dev3+g1234567
    (3 commits after tag)
toml
[build-system]
requires = ["setuptools>=61.0", "setuptools-scm>=8.0"]
build-backend = "setuptools.build_meta"

[project]
name = "my-package"
dynamic = ["version"]

[tool.setuptools_scm]
write_to = "src/my_package/_version.py"
version_scheme = "post-release"
local_scheme = "dirty-tag"
生成的版本示例:
  • 1.0.0
    (来自Git标签)
  • 1.0.1.dev3+g1234567
    (标签之后的第3次提交)

Testing Installation

安装测试

Pattern 16: Editable Install

模式16:可编辑安装

bash
undefined
bash
undefined

Install in development mode

以开发模式安装

pip install -e .
pip install -e .

With optional dependencies

包含可选依赖

pip install -e ".[dev]" pip install -e ".[dev,docs]"
pip install -e ".[dev]" pip install -e ".[dev,docs]"

Now changes to source code are immediately reflected

此时源码修改会立即生效

undefined
undefined

Pattern 17: Testing in Isolated Environment

模式17:在隔离环境中测试

bash
undefined
bash
undefined

Create virtual environment

创建虚拟环境

python -m venv test-env source test-env/bin/activate # Linux/Mac
python -m venv test-env source test-env/bin/activate # Linux/Mac系统

test-env\Scripts\activate # Windows

test-env\Scripts\activate # Windows系统

Install package

安装包

pip install dist/my_package-1.0.0-py3-none-any.whl
pip install dist/my_package-1.0.0-py3-none-any.whl

Test it works

测试包是否可用

python -c "import my_package; print(my_package.version)"
python -c "import my_package; print(my_package.version)"

Test CLI

测试CLI工具

my-tool --help
my-tool --help

Cleanup

清理环境

deactivate rm -rf test-env
undefined
deactivate rm -rf test-env
undefined

Documentation

文档

Pattern 18: README.md Template

模式18:README.md模板

markdown
undefined
markdown
undefined

My Package

My Package

PyPI version Python versions Tests
Brief description of your package.
PyPI version Python versions Tests
包的简短描述。

Installation

安装

bash
pip install my-package
undefined
bash
pip install my-package
undefined

Quick Start

快速开始

python
from my_package import something

result = something.do_stuff()
python
from my_package import something

result = something.do_stuff()

Features

功能特性

  • Feature 1
  • Feature 2
  • Feature 3
  • 特性1
  • 特性2
  • 特性3

Documentation

文档

Development

开发

bash
git clone https://github.com/username/my-package.git
cd my-package
pip install -e ".[dev]"
pytest
bash
git clone https://github.com/username/my-package.git
cd my-package
pip install -e ".[dev]"
pytest

License

许可证

MIT
undefined
MIT
undefined

Common Patterns

常见模式

Pattern 19: Multi-Architecture Wheels

模式19:多架构Wheel包

yaml
undefined
yaml
undefined

.github/workflows/wheels.yml

.github/workflows/wheels.yml

name: Build wheels
on: [push, pull_request]
jobs: build_wheels: name: Build wheels on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest]
steps:
  - uses: actions/checkout@v3

  - name: Build wheels
    uses: pypa/cibuildwheel@v2.16.2

  - uses: actions/upload-artifact@v3
    with:
      path: ./wheelhouse/*.whl
undefined
name: 构建Wheel包
on: [push, pull_request]
jobs: build_wheels: name: 在${{ matrix.os }}上构建Wheel包 runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest]
steps:
  - uses: actions/checkout@v3

  - name: 构建Wheel包
    uses: pypa/cibuildwheel@v2.16.2

  - uses: actions/upload-artifact@v3
    with:
      path: ./wheelhouse/*.whl
undefined

Pattern 20: Private Package Index

模式20:私有包索引

bash
undefined
bash
undefined

Install from private index

从私有索引安装包

pip install my-package --index-url https://private.pypi.org/simple/
pip install my-package --index-url https://private.pypi.org/simple/

Or add to pip.conf

或添加至pip.conf文件

[global] index-url = https://private.pypi.org/simple/ extra-index-url = https://pypi.org/simple/
[global] index-url = https://private.pypi.org/simple/ extra-index-url = https://pypi.org/simple/

Upload to private index

上传至私有索引

twine upload --repository-url https://private.pypi.org/ dist/*
undefined
wine upload --repository-url https://private.pypi.org/ dist/*
undefined

File Templates

文件模板

.gitignore for Python Packages

Python包的.gitignore模板

gitignore
undefined
gitignore
undefined

Build artifacts

构建产物

build/ dist/ *.egg-info/ *.egg .eggs/
build/ dist/ *.egg-info/ *.egg .eggs/

Python

Python文件

pycache/ *.py[cod] *$py.class *.so
pycache/ *.py[cod] *$py.class *.so

Virtual environments

虚拟环境

venv/ env/ ENV/
venv/ env/ ENV/

IDE

IDE配置

.vscode/ .idea/ *.swp
.vscode/ .idea/ *.swp

Testing

测试相关

.pytest_cache/ .coverage htmlcov/
.pytest_cache/ .coverage htmlcov/

Distribution

分发包

*.whl *.tar.gz
undefined
*.whl *.tar.gz
undefined

MANIFEST.in

MANIFEST.in模板

undefined
undefined

MANIFEST.in

MANIFEST.in

include README.md include LICENSE include pyproject.toml
recursive-include src/my_package/data *.json recursive-include src/my_package/templates *.html recursive-exclude * pycache recursive-exclude * *.py[co]
undefined
include README.md include LICENSE include pyproject.toml
recursive-include src/my_package/data *.json recursive-include src/my_package/templates *.html recursive-exclude * pycache recursive-exclude * *.py[co]
undefined

Checklist for Publishing

发布检查清单

  • Code is tested (pytest passing)
  • Documentation is complete (README, docstrings)
  • Version number updated
  • CHANGELOG.md updated
  • License file included
  • pyproject.toml is complete
  • Package builds without errors
  • Installation tested in clean environment
  • CLI tools work (if applicable)
  • PyPI metadata is correct (classifiers, keywords)
  • GitHub repository linked
  • Tested on TestPyPI first
  • Git tag created for release
  • 代码已测试(pytest用例全部通过)
  • 文档完整(README、文档字符串)
  • 版本号已更新
  • CHANGELOG.md已更新
  • 包含LICENSE文件
  • pyproject.toml配置完整
  • 包可正常构建
  • 已在干净环境中测试安装
  • CLI工具可正常使用(若有)
  • PyPI元数据正确(分类器、关键词)
  • 已关联GitHub仓库
  • 已在TestPyPI测试
  • 已为版本创建Git标签

Resources

参考资源

Best Practices Summary

最佳实践总结

  1. Use src/ layout for cleaner package structure
  2. Use pyproject.toml for modern packaging
  3. Pin build dependencies in build-system.requires
  4. Version appropriately with semantic versioning
  5. Include all metadata (classifiers, URLs, etc.)
  6. Test installation in clean environments
  7. Use TestPyPI before publishing to PyPI
  8. Document thoroughly with README and docstrings
  9. Include LICENSE file
  10. Automate publishing with CI/CD
  1. 使用src/布局:更清晰的包结构
  2. 使用pyproject.toml:遵循现代打包标准
  3. 固定构建依赖:在build-system.requires中指定
  4. 合理版本管理:使用语义化版本
  5. 完善元数据:包含分类器、URL等信息
  6. 在干净环境测试安装:避免依赖问题
  7. 先在TestPyPI测试:再发布至正式环境
  8. 充分文档化:编写README和文档字符串
  9. 包含LICENSE文件:明确许可证
  10. 自动化发布:使用CI/CD流程