从零开始:用uv构建并发布一个Python CLI应用,集成CI/CD自动化发布与Docker容器化部署

概述

从一个空目录开始,使用 uv 创建、开发、构建并发布一个名为 ccyy-demo 的命令行调用的工具

初始化项目

uv 提供了 uv init 命令,可以快速生成一个遵循最佳实践的项目骨架。使用 --package 参数可以直接创建标准的 src 布局。

1.基于已有项目进行初始化

bash 复制代码
# 创建并进入项目根目录
mkdir ccyy-demo
cd ccyy-demo

# 在当前项目下初始化并生成标准目录结构
# 会自动创建虚拟环境 (.venv)、pyproject.toml, 以及src/ccyy_demo/__init__.py源代码结构
uv init . --package

2.使用uv创建并初始化项目

bash 复制代码
uv init --package ccyy_demo
# 需单独创建venv环境
uv venv

现在,你的目录结构应该是这样的:

css 复制代码
ccyy-demo/
├── .venv/
├── pyproject.toml
├── .python-version
├── .gitignore
├── README.md
└── src/
    └── ccyy_demo/
        ├── __init__.py

编写应用代码

创建 src/ccyy_demo/main.py 文件,写入命令行调用工具的代码核心逻辑。将使用 argparse 来处理命令行参数。

python 复制代码
import argparse

def main():
    """CLI 工具的主入口函数"""
    parser = argparse.ArgumentParser(
        description="一个由 ccyy-demo 创建的、使用 uv 构建的简单 CLI 工具。"
    )
    parser.add_argument(
        "--name",
        default="World",
        help="The name to greet."
    )
    args = parser.parse_args()
    print(f"Hello, {args.name}! This is ccyy-demo speaking.")

if __name__ == "__main__":
    main()

src/ccyy_demo/__init__.py的执行入口添加实现函数

bash 复制代码
from .main import main as ccyy_demo_main

def main() -> None:
    ccyy_demo_main()

定义项目 (pyproject.toml)

pyproject.toml是现代Python项目的核心配置文件,它遵循 PEP 621 标准,用于定义项目的元数据、依赖项和构建系统。

uv init 已经为生成了一个 pyproject.toml模板,默认内容如下:

bash 复制代码
[project]
name = "ccyy-demo"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.10"
dependencies = []

[project.scripts]
ccyy-demo = "ccyy_demo:main"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

扩展后的pyproject.toml

bash 复制代码
# 包含项目的核心元数据,如名称、版本、描述等。这些信息会用于PyPI(Python 包索引)以及包管理工具。
[project]
# 项目名称,在PyPI上发布时不能有冲突
# 通常使用连字符 "-" 而不是下划线 "_"
name = "ccyy-demo"
# 项目的当前版本号,遵循语义化版本
version = "0.1.0"
# 项目的简短描述
description = "一个使用 uv 构建的示例 CLI 项目"
# 项目的详细描述,通常从 README.md 文件中读取
readme = "README.md"
# 项目所需的最低 Python 版本
requires-python = ">=3.10"
# 项目的许可证信息
license = { text = "MIT" }
# 项目作者信息
authors = [
    { name = "Your Name", email = "your@email.com" }
]
# 项目的关键词,有助于在PyPI上被搜索到。
keywords = ["cli", "demo", "uv", "ccyy"]

# 可以添加一些有用的链接,比如项目主页、文档、代码仓库等
[project.urls]
Homepage = "https://github.com/your-username/ccyy-demo"
Repository = "https://github.com/your-username/ccyy-demo"
Bug Tracker = "https://github.com/your-username/ccyy-demo/issues"

# 列表定义了项目运行所必需的核心依赖,这些包会在用户安装项目时自动安装
dependencies = [
    "xxx>=8.0",
]

# 定义了安装包后可执行的命令行脚本,当用户安装包后,就可以直接在终端中运行这里的命令
# 格式: `command-name = "path.to.module:function_name"`
[project.scripts]
ccyy-mcp = "ccyy_demo.main:main"

# 定义了可选的依赖组,这对于区分开发环境、测试环境和生产环境的依赖非常有用
[project.optional-dependencies]
# "dev" 组通常包含开发过程中需要的工具,如代码检查、格式化、测试等
# 用户可以通过 `pip install .[dev]` 来安装这些依赖
dev = [
    "ruff",      # 高性能的代码检查和格式化工具
    "pytest",    # 强大的 Python 测试框架
    "mypy",      # 静态类型检查工具
]

# 定义了项目的构建工具。这是pyproject.toml的强制性部分,告诉pip等工具如何构建项目包
[build-system]
# requires指定构建项目所需的包列表。
# "hatchling"是一个现代、快速且功能强大的Python项目构建后端
requires = ["hatchling"]
# build-backend指定用于执行构建过程的Python对象
build-backend = "hatchling.build"

使用uv安装依赖

uv init已经创建了虚拟环境。只需激活它并安装依赖。激活成功后,会在终端提示符前看到 (.venv) 的字样。

bash 复制代码
# 1.  激活虚拟环境
# Windows (CMD)
.venv\Scripts\activate

# macOS / Linux
source .venv/bin/activate

# 2. 安装项目依赖
uv pip install -e .[dev]

# 3.如果只想安装运行时的核心依赖,可以省略 `[dev]`
uv pip install -e .

命令解析:

  • -e: 可编辑模式。这是开发的核心。它会创建一个指向你源代码的链接,而不是复制文件。这意味着你对源代码的任何修改都会立刻生效,无需重新安装

  • .: 代表当前目录下的项目

  • [dev]: 告诉安装器,除了项目的基本依赖外,还要安装 [project.optional-dependencies]dev 组的所有包

本地运行和测试

由于在 [project.scripts] 中定义了ccyy-demo,所以在安装后(特别是可编辑模式下,-e 模式),可以直接在终端中运行它。

bash 复制代码
# 直接运行,使用默认参数 "World"
ccyy-demo
# > Hello, World! This is ccyy-demo speaking.

# 传入参数
ccyy-demo --name Jack
# > Hello, Jack! This is ccyy-demo speaking.

依赖锁定

在发布前,生成一个精确的依赖锁文件,以保证生产环境的确定性。

1.编译生产依赖

直接编译pyproject.toml,uv会自动读取[project.dependencies]部分。

bash 复制代码
uv pip compile pyproject.toml -o requirements.txt

2.编译开发依赖

编译可选依赖。如果想为开发环境生成一个锁定文件,可以使用--extra标志。

bash 复制代码
uv pip compile pyproject.toml --extra dev -o dev-requirements.txt

为了确保开发依赖和生产依赖的版本兼容,通常会让开发依赖文件引用生产依赖文件:

编译时,让dev-requirements.txt引用并遵循requirements.txt中的版本约束

bash 复制代码
uv pip compile pyproject.toml --extra dev -o dev-requirements.txt -c requirements.txt

3.安装锁定的依赖

一旦有了requirements.txt文件,就可以使用uv pip sync来安装它。sync命令会确保虚拟环境与 requirements.txt文件完全一致,不多也不少。

bash 复制代码
# 激活虚拟环境
# Linux/Mac
source .venv/bin/activate  

# Windows:
.venv\Scripts\activate

# 使用 sync 安装锁定的依赖
uv pip sync -r requirements.txt

构建

使用 uv build命令来构建项目。这个命令会创建一个源代码发行版 (tar.gz) 和一个wheel文件,被放置在项目根目录下的一个名为dist的文件夹里

bash 复制代码
uv build

还可以通过传递参数来指定构建的wheel的目标Python版本:

bash 复制代码
uv build --python 3.9

更多关于uv build的使用参考帮助命令

bash 复制代码
uv build -h

发布

首先,访问Pypi官网,在Account settings菜单栏下找到API tokens并执行Add API token进行添加一个Token 然后,可以使用uv publish命令将包发布到PyPI或其他包索引。

bash 复制代码
uv publish --token Token

更多关于uv publish 的使用参考帮助命令

bash 复制代码
uv publish -h

生产环境实践之CI/CD

自动化测试和发布是生产级项目的标配,以GitHub Actions为例。

创建工作流配置文件

1.通过GitHub网页界面创建

bash 复制代码
进入 GitHub 仓库

点击 "Add file" → "Create new file"

在文件名输入框中输入:.github/workflows/publish.yml

将 YAML 配置内容粘贴到编辑器中

点击 "Commit new file"

2.通过本地Git创建并推送到远程仓库

bash 复制代码
# 在项目根目录创建文件夹结构
mkdir -p .github/workflows

# 创建配置文件
touch .github/workflows/publish.yml

publish.yml具体配置内容如下:

yaml 复制代码
# --------------------------------------------------
# 工作流的全局配置
# --------------------------------------------------

# name: 定义工作流的名称。这个名称会显示在你的 GitHub 仓库的 "Actions" 标签页中,方便识别和管理这个自动化流程。
name: Publish Python Package to PyPI

# --------------------------------------------------
# 触发器 (Triggers)
# --------------------------------------------------

# on: 定义触发这个工作流的事件。可以配置多种触发条件。
on:
  # push: 表示当代码被推送到仓库时触发。
  push:
    # tags: 进一步限制,只有当推送的是一个 "标签 (tag)" 时才触发。
    # 这是一种常见的发布策略,确保只有打了版本标签的提交才会被发布。
    tags:
      # - 'v*': 这是一个模式匹配规则。'*' 是通配符。
      # 意味着只有当标签以 'v' 开头时(例如 v1.0, v2.1.3),这个工作流才会被触发。
      # 推送普通的代码提交或不匹配的标签(如 'release-1')不会触发此流程。
      - 'v*'

# --------------------------------------------------
# 任务 (Jobs)
# --------------------------------------------------

# jobs: 定义工作流中要执行的一个或多个任务。
# 任务可以并行或按顺序运行。这里我们只有一个名为 "publish" 的任务。
jobs:
  # publish: 这是我们为这个任务写的唯一标识符(ID)。你可以自定义,如 'deploy'。
  publish:
    # name: 为任务定义一个更友好的显示名称,它会显示在 GitHub Actions 的 UI 界面中。
    name: Build and publish to PyPI

    # runs-on: 指定运行这个任务的虚拟机环境。
    # GitHub 会提供临时的云服务器来执行任务。
    # 'ubuntu-latest' 表示使用最新稳定版的 Ubuntu Linux 系统,这是 Python 项目最常用的环境。
    runs-on: ubuntu-latest

    # --------------------------------------------------
    # 步骤 (Steps)
    # --------------------------------------------------

    # steps: 定义了构成这个任务的一系列按顺序执行的步骤。
    # 每个步骤可以是一个独立的命令,也可以是使用一个预定义的 "Action"。
    steps:
      # 步骤 1: 检出代码
      # - name: (可选) 为步骤提供一个描述性名称,会显示在日志中。
      - name: Check out repository
        # uses: 指定使用一个社区或官方提供的可复用 Action。
        # 'actions/checkout@v4' 是一个官方 Action,
        # 它的功能是将你的仓库代码下载到虚拟机的工作目录中,以便后续步骤可以访问它。
        uses: actions/checkout@v4

      # 步骤 2: 设置 Python 环境
      - name: Set up Python
        # 'actions/setup-python@v5' 是另一个官方 Action,
        # 专门用于在虚拟机中安装并配置指定版本的 Python。
        uses: actions/setup-python@v5
        # with: 用于向 Action 传递参数。
        with:
          # python-version: 指定要安装的 Python 版本。
          # 建议使用你的项目所支持和测试过的具体版本。
          python-version: '3.11'

      # 步骤 3: 安装 uv 工具
      # uv 是一个由 Astral 开发的高性能 Python 包管理工具,可以替代 pip、pip-tools、virtualenv 和 venv。
      # 它以极快的速度著称,在这里我们用它来构建和发布包。
      - name: Install uv publishing tool
        # 'astral-sh/setup-uv@v2' 是 uv 官方提供的 Action,用于在环境中快速安装 uv。
        uses: astral-sh/setup-uv@v2
        with:
          # version: 指定要安装的 uv 版本,'latest' 表示始终使用最新版。
          version: "latest"

      # 步骤 4: 构建包
      # 在发布之前,需要将项目源代码打包成标准的 Python 发行版格式(sdist 和 wheel)。
      - name: Build package
        # run: 指定直接在虚拟机的命令行 (shell) 中执行一条命令。
        # 'uv build' 会读取项目根目录下的 'pyproject.toml' 文件,
        # 然后在 'dist/' 目录下生成 .whl (wheel) 和 .tar.gz (sdist) 文件。
        run: uv build

      # 步骤 5: 发布到 PyPI
      # 这是最后一步,将构建好的包上传到 Python 包索引 (PyPI)。
      - name: Publish package to PyPI
        # 'uv publish' 命令会自动查找 'dist/' 目录下的包文件并上传。
        # '--token' 是一个命令行参数,用于提供认证凭据。
        # '${{ secrets.PYPI_API_TOKEN }}' 是 GitHub Actions 的特殊语法,用于安全地引用密钥。
        # 它会从你仓库的 "Settings -> Secrets and variables -> Actions" 中读取名为 'PYPI_API_TOKEN' 的值。
        # 使用 Secrets 可以确保你的 API Token 不会暴露在代码或公开的日志中。
        run: uv publish --token ${{ secrets.PYPI_API_TOKEN }}

配置GitHub Secrets

进入GitHub仓库

makefile 复制代码
点击Settings > Secrets and variables > Actions

在Repository secrets部分,创建一个名为 PYPI_API_TOKEN 的 Secret

Name: 必须为PYPI_API_TOKEN,需与工作流配置文件中保持一致

Secret: 粘贴从PyPI官网复制的、以 pypi- 开头的完整 Token

点击  Add secret

创建和推送tag

bash 复制代码
# 打标签
git tag v1.0.0

# 推送标签到远程仓库
git push origin v1.0.0

验证发布

推送tag后,进入 GitHub 仓库的 Actions 标签页 查看 Publish to PyPI 工作流的运行状态 如果成功,构建的包将出现在 PyPI 上

生产环境实践之Docker

创建Dockerfile

根据上述Python CLI应用创建对应适合的Dockerfile文件

yaml 复制代码
# 1. 选择一个基础镜像 使用UV官方提供的镜像
FROM ghcr.io/astral-sh/uv:python3.11-alpine

# 2. 设置工作目录
# 在容器内创建一个 /app 目录,并将其设置为后续所有命令的执行目录。
WORKDIR /app

# 3. 复制所有项目文件
# 将本地项目目录下的所有文件复制到容器的 /app 目录中。
# '.' 代表本地的当前目录,第二个 '.' 代表容器内的工作目录 (/app)。
COPY . .

# 4.创建虚拟环境并安装依赖
RUN uv sync 

# 5. 激活虚拟环境(设置PATH)
ENV PATH="/app/.venv/bin:$PATH"

# 6. 设置容器启动时要执行的默认命令
# 当运行 `docker run <镜像名>` 时,容器会自动执行这个命令。
CMD ["ccyy-demo", "--name", "Docker User"]

构建镜像

bash 复制代码
root@master:~/ccyy_demo# docker images
REPOSITORY                            TAG                  IMAGE ID       CREATED          SIZE
demo                                  0.1                  59c234aaad24   3 minutes ago    105MB

运行容器

bash 复制代码
root@master:~/ccyy_demo# docker run demo:0.1
Hello, Docker User! This is ccyy-demo speaking.
相关推荐
文人sec6 小时前
性能测试-jmeter7-元件提取器
python·jmeter·prometheus·模块测试
橙子小哥的代码世界6 小时前
【Docker】2025版Ubuntu 22.04 安装 Docker & Docker Compose 指南
linux·ubuntu·docker·微服务·容器编排·docker engine·docker compose
这里有鱼汤6 小时前
量化小白必看|MiniQMT踩坑记:想做实盘这些知识请你一定要掌握
后端·python
数据小子2146 小时前
【自记】Python 中 nonlocal 和 global 的区别,以及闭包的作用和示例
python
WSSWWWSSW6 小时前
Python OpenCV图像处理与深度学习:Python OpenCV视频处理入门
图像处理·python·opencv
胖墩会武术6 小时前
【PyTorch项目实战】SAM(Segment Anything Model) —— 致力于建立第一个图像分割基础模型
人工智能·pytorch·python·sam
极小狐6 小时前
GitLab 18.3 正式发布,更新多项 DevOps、CI/CD 功能【一】
ci/cd·gitlab·devsecops·devops·极狐gitlab
励志不掉头发的内向程序员7 小时前
从零开始的python学习——函数(1)
开发语言·python·学习
闲人编程7 小时前
雪花算法实现分布式环境下的高效动态ID生成
分布式·python·算法·wpf·标识符·雪花·分布式动态id