发布于2026-07-28 阅读(0)
扫一扫,手机访问
Python 包发布到 PyPI,是每个 Python 开发者迟早要面对的事情。传统流程涉及 pip、pip-tools、poetry、twine 等多个工具,配置繁琐。不过,现在有了 uv——一个超快的 Python 包管理器,它把这些环节全部整合在了一起,整个过程变得高效又流畅。下面,我们一步步来看如何用 uv 完成从项目创建到发布的全流程。

pyproject.tomlmacOS 和 Linux:
curl -LsSf https://astral.sh/uv/install.sh | sh
Windows (PowerShell):
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
通过 pip 安装:
pip install uv
uv --version
# 安装特定版本的 Python uv python install 3.12 # 查看已安装的 Python 版本 uv python list
# 创建新项目 uv init my-awesome-package # 进入项目目录 cd my-awesome-package # 项目结构会自动创建
my-awesome-package/ ├── src/ │ └── my_awesome_package/ │ ├── __init__.py │ └── core.py ├── tests/ │ ├── __init__.py │ └── test_core.py ├── pyproject.toml ├── README.md ├── LICENSE └── .gitignore
创建目录结构:
mkdir -p my-awesome-package/src/my_awesome_package mkdir -p my-awesome-package/tests cd my-awesome-package
pyproject.toml 是 Python 项目的核心配置文件,包含三个主要部分:
[build-system]
requires = ["hatchling >= 1.26"]
build-backend = "hatchling.build"
[project]
name = "my-awesome-package"
version = "0.1.0"
authors = [
{ name = "Your Name", email = "your.email@example.com" }
]
maintainers = [
{ name = "Your Name", email = "your.email@example.com" }
]
description = "A short description of your package"
readme = "README.md"
requires-python = ">=3.9"
license = "MIT"
license-files = ["LICEN[CS]E*"]
keywords = ["example", "package", "tutorial"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
dependencies = [
"requests>=2.28.0",
"pydantic>=2.0.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
"pytest-cov>=4.0.0",
"ruff>=0.1.0",
"mypy>=1.0.0",
]
docs = [
"mkdocs>=1.5.0",
"mkdocs-material>=9.0.0",
]
[project.urls]
Homepage = "https://github.com/yourusername/my-awesome-package"
Documentation = "https://github.com/yourusername/my-awesome-package#readme"
Repository = "https://github.com/yourusername/my-awesome-package"
Issues = "https://github.com/yourusername/my-awesome-package/issues"
Changelog = "https://github.com/yourusername/my-awesome-package/blob/main/CHANGELOG.md"
[project.scripts]
my-cli = "my_awesome_package.cli:main"
[project.gui-scripts]
my-gui = "my_awesome_package.gui:main"
[tool.hatch.build.targets.wheel]
packages = ["src/my_awesome_package"]
[tool.ruff]
line-length = 88
target-version = "py39"
[tool.ruff.lint]
select = ["E", "F", "I", "N", "W", "UP"]
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-v --cov=my_awesome_package"
[tool.mypy]
python_version = "3.9"
warn_return_any = true
warn_unused_configs = true
[build-system] requires = ["hatchling >= 1.26"] build-backend = "hatchling.build"
常用的构建后端:
| 构建后端 | requires | build-backend |
|---|---|---|
| Hatchling (推荐) | "hatchling >= 1.26" | "hatchling.build" |
| Setuptools | "setuptools >= 77.0.3" | "setuptools.build_meta" |
| Flit | "flit_core >= 3.12.0, <4" | "flit_core.buildapi" |
| PDM | "pdm-backend >= 2.4.0" | "pdm.backend" |
| uv-build | "uv_build >= 0.11.7, <0.12.0" | "uv_build" |
| 字段 | 说明 | 示例 |
|---|---|---|
name | 包名(PyPI 上的名称) | "my-awesome-package" |
version | 版本号 | "0.1.0" |
description | 简短描述 | "A short description" |
readme | README 文件路径 | "README.md" |
requires-python | Python 版本要求 | ">=3.9" |
license | SPDX 许可证表达式 | "MIT" |
dependencies | 运行时依赖 | ["requests>=2.28.0"] |
[project.urls] Homepage = "https://github.com/user/repo" Documentation = "https://readthedocs.org" Repository = "https://github.com/user/repo" Issues = "https://github.com/user/repo/issues"
[project.scripts] my-cli = "my_package.module:function"
安装后会创建一个 my-cli 命令。
[project.optional-dependencies] dev = ["pytest", "ruff"] docs = ["mkdocs"]
安装时使用:pip install my-package[dev]
src/my_awesome_package/init.py:
"""My Awesome Package - A short description.""" __version__ = "0.1.0" __author__ = "Your Name" __email__ = "your.email@example.com" from .core import hello, add_numbers __all__ = ["hello", "add_numbers", "__version__"]
src/my_awesome_package/core.py:
"""Core functionality of my awesome package."""
def hello(name: str = "World") -> str:
"""Say hello to someone.
Args:
name: The name to greet. Defaults to "World".
Returns:
A greeting string.
"""
return f"Hello, {name}!"
def add_numbers(a: int | float, b: int | float) -> int | float:
"""Add two numbers together.
Args:
a: First number.
b: Second number.
Returns:
The sum of a and b.
"""
return a + b
class Calculator:
"""A simple calculator class."""
def __init__(self) -> None:
self.history: list[str] = []
def add(self, a: float, b: float) -> float:
"""Add two numbers."""
result = a + b
self.history.append(f"{a} + {b} = {result}")
return result
def subtract(self, a: float, b: float) -> float:
"""Subtract b from a."""
result = a - b
self.history.append(f"{a} - {b} = {result}")
return result
def get_history(self) -> list[str]:
"""Get calculation history."""
return self.history.copy()
src/my_awesome_package/cli.py:
"""Command-line interface for my awesome package."""
import argparse
import sys
from . import __version__
from .core import hello
def main() -> int:
"""Main entry point for the CLI."""
parser = argparse.ArgumentParser(
description="My Awesome Package CLI"
)
parser.add_argument(
"--version",
action="version",
version=f"%(prog)s {__version__}"
)
parser.add_argument(
"--name",
default="World",
help="Name to greet"
)
args = parser.parse_args()
print(hello(args.name))
return 0
if __name__ == "__main__":
sys.exit(main())
tests/test_core.py:
"""Tests for core module."""
import pytest
from my_awesome_package.core import Calculator, add_numbers, hello
class TestHello:
"""Tests for hello function."""
def test_hello_default(self):
assert hello() == "Hello, World!"
def test_hello_with_name(self):
assert hello("Alice") == "Hello, Alice!"
def test_hello_empty_string(self):
assert hello("") == "Hello, !"
class TestAddNumbers:
"""Tests for add_numbers function."""
def test_add_positive_integers(self):
assert add_numbers(2, 3) == 5
def test_add_negative_integers(self):
assert add_numbers(-1, -1) == -2
def test_add_floats(self):
assert add_numbers(1.5, 2.5) == 4.0
def test_add_mixed(self):
assert add_numbers(1, 2.5) == 3.5
class TestCalculator:
"""Tests for Calculator class."""
def test_add(self):
calc = Calculator()
assert calc.add(2, 3) == 5
def test_subtract(self):
calc = Calculator()
assert calc.subtract(5, 3) == 2
def test_history(self):
calc = Calculator()
calc.add(1, 2)
calc.subtract(5, 3)
history = calc.get_history()
assert len(history) == 2
assert "1 + 2 = 3" in history[0]
# My Awesome Package [](https://pypi.org/project/my-awesome-package/) [](https://pypi.org/project/my-awesome-package/) A short description of your package. ## Installation ```bash pip install my-awesome-package
--- ## 构建包 ### 1. 使用 uv 管理依赖 ```bash # 添加运行时依赖 uv add requests pydantic # 添加开发依赖 uv add --dev pytest ruff mypy # 同步依赖(安装所有依赖) uv sync # 只安装特定可选依赖 uv sync --extra dev
# 运行测试 uv run pytest # 运行测试并生成覆盖率报告 uv run pytest --cov=my_awesome_package --cov-report=html
# 运行 linter uv run ruff check . # 运行 formatter uv run ruff format . # 运行类型检查 uv run mypy src/
# 构建 sdist 和 wheel uv build # 构建时不使用本地源码覆盖(推荐用于发布) uv build --no-sources
构建完成后,会在 dist/ 目录下生成:
dist/ ├── my_awesome_package-0.1.0.tar.gz # 源码分发包 (sdist) └── my_awesome_package-0.1.0-py3-none-any.whl # wheel 包
# 检查包的元数据 uv pip show --files dist/my_awesome_package-0.1.0-py3-none-any.whl # 使用 twine 检查(可选) pip install twine twine check dist/*
Token 格式:pypi-AgEI...
方法一:环境变量(推荐)
export UV_PUBLISH_TOKEN="pypi-your-token-here"
方法二:命令行参数
uv publish --token "pypi-your-token-here"
方法三:使用 .pypirc 文件
创建 ~/.pypirc:
[pypi] username = __token__ password = pypi-your-token-here [testpypi] username = __token__ password = pypi-your-testpypi-token-here
设置文件权限:
chmod 600 ~/.pypirc
# 发布所有构建产物 uv publish # 使用 Token 发布 uv publish --token "pypi-your-token-here" # 发布到指定索引(需要在 pyproject.toml 中配置) uv publish --index pypi
发布成功后,访问 https://pypi.org/project/my-awesome-package/ 查看你的包。
安装测试:
# 在新环境中测试安装 uv run --with my-awesome-package --no-project -- python -c "from my_awesome_package import hello; print(hello())"
在正式发布前,建议先在 TestPyPI 上测试。
访问 https://test.pypi.org/account/register/
在 pyproject.toml 中添加:
[[tool.uv.index]] name = "testpypi" url = "https://test.pypi.org/simple/" publish-url = "https://test.pypi.org/legacy/" explicit = true
# 生成 TestPyPI Token 后 uv publish --index testpypi --token "pypi-your-testpypi-token"
# 创建测试环境 uv venv test-env source test-env/bin/activate # Linux/macOS # test-envScriptsactivate # Windows # 从 TestPyPI 安装 uv pip install --index-url https://test.pypi.org/simple/ my-awesome-package # 测试导入 python -c "from my_awesome_package import hello; print(hello())"
# 查看当前版本 uv version # 设置精确版本 uv version 1.0.0 # 预览版本变更(不实际修改) uv version 2.0.0 --dry-run # 语义化版本升级 uv version --bump patch # 0.1.0 -> 0.1.1 uv version --bump minor # 0.1.0 -> 0.2.0 uv version --bump major # 0.1.0 -> 1.0.0 # 预发布版本 uv version --bump patch --bump beta # 0.1.0 -> 0.1.1b1 uv version --bump major --bump alpha # 0.1.0 -> 1.0.0a1 # 从预发布升级到稳定版 uv version --bump stable # 1.0.0b2 -> 1.0.0
遵循 语义化版本:
预发布版本:1.0.0a1, 1.0.0b2, 1.0.0rc1
推荐使用 src 布局:
my-package/ ├── src/ │ └── my_package/ │ ├── __init__.py │ ├── core.py │ └── py.typed # PEP 561 类型标记 ├── tests/ ├── pyproject.toml ├── README.md ├── LICENSE └── .gitignore
# 明确指定版本范围
dependencies = [
"requests>=2.28.0,<3.0.0",
"pydantic>=2.0.0",
]
# 分离开发依赖
[project.optional-dependencies]
dev = ["pytest>=7.0", "ruff>=0.1.0"]
GitHub Actions 示例 (.github/workflows/publish.yml):
name: Publish to PyPI
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
permissions:
id-token: write # 用于可信发布
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Set up Python
run: uv python install 3.12
- name: Run tests
run: |
uv sync --extra dev
uv run pytest
- name: Build package
run: uv build --no-sources
- name: Publish to PyPI
run: uv publish
env:
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
PyPI 支持无需 Token 的可信发布:
id-token: write 权限- name: Publish to PyPI run: uv publish # 不需要设置 UV_PUBLISH_TOKEN
发布前确认:
# 完整的发布流程 uv run pytest && uv run ruff check . && uv build --no-sources && twine check dist/* && uv publish
yourname-packagename)# 清理构建缓存 rm -rf dist/ build/ *.egg-info # 重新构建 uv build --no-sources # 查看详细错误 uv build --verbose
PyPI 不允许删除已发布的版本,只能:
yanking 标记版本为不推荐如果包含 C/C++ 扩展,需要使用 cibuildwheel:
# GitHub Actions - name: Build wheels uses: pypa/cibuildwheel@v2.16
# 更新版本号 uv version --bump patch # 重新构建并发布 uv build --no-sources uv publish
# pyproject.toml [[tool.uv.index]] name = "private" url = "https://private.pypi.org/simple/" publish-url = "https://private.pypi.org/legacy/"
uv publish --index private
# 项目初始化 uv init my-package cd my-package # 依赖管理 uv add requests uv add --dev pytest uv sync # 运行代码 uv run python -m my_package uv run pytest # 版本管理 uv version uv version --bump patch # 构建和发布 uv build --no-sources uv publish # 发布到 TestPyPI uv publish --index testpypi
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8