-
如何构建自己的Python包
在日常开发中,我们经常会把一些通用的函数、类或者模块抽取出来,以便在不同的项目中复用。随着代码的积累,如何将这些工具封装为一个标准的 Python 包(Python Package),并且方便团队甚至社区使用,就成为一个值得思考的问题。本文将从零开始,介绍如何构建、打包和发布自己的 Python 包。
为什么要构建 Python 包?
-
代码复用:将常用的工具函数和逻辑沉淀为独立模块,避免复制粘贴。
-
团队协作:通过 PyPI 或内部源发布,团队成员可以直接 pip install 使用。
-
版本管理:独立的包可以有清晰的版本迭代,不影响主项目。
-
生态兼容:遵循 Python 社区标准,可以更好地融入现有生态。
单仓库多模块
在实际运维开发中,往往不仅仅是一个独立的 Python 包,而是需要在同一个仓库(monorepo) 中维护多个模块(package)。这种模式能够更好地统一管理依赖、版本以及 CI/CD 流程,适合团队协作和多服务共存的场景。
目录结构示例
├── MANIFEST.in ├── README.md ├── docs ├── exec.py ├── publish.sh ├── pyproject.toml ├── src │ ├── pytbox │ │ ├── __pycache__ │ │ ├── alert │ │ ├── alicloud │ │ ├── base.py │ │ ├── categraf │ │ ├── cli │ │ ├── cli.py │ │ ├── common │ │ ├── database │ │ ├── dida365.py │ │ ├── feishu │ │ ├── log │ │ ├── onepassword_connect.py │ │ ├── onepassword_sa.py │ │ └── utils │ └── pytbox.egg-info │ ├── PKG-INFO │ ├── SOURCES.txt │ ├── dependency_links.txt │ ├── entry_points.txt │ ├── requires.txt │ └── top_level.txt └── tests ├── alert │ ├── __pycache__ │ ├── config_dev.toml │ ├── test_ping.py │ └── test_traffic.py ├── categraf │ ├── conf │ └── instances.toml ├── conftest.py ├── test_base.py ├── test_feishu.py ├── test_logger.py ├── test_onepassword_connect.py ├── test_onepassword_sa.py ├── test_victoriametrics.py └── utils ├── __pycache__ └── test_timeutils.py 29 directories, 38 files
开发环境搭建
我使用的是 devcontainer 作为开发环境,可以用 VSCode 远程到 Linux 并配置 devcontainer,可以解决环境本身,可以随时更换 Python 版本,还能解决多人协作开发时,环境配置不一致的问题。 通过 VSCode 的 Remote SSH 登录到一台 Linux Server,需要先安装 Docker 环境 在项目根目录下创建 .devcontainer 文件夹,并创建 devcontainer.json 文件,由于内容较长,可以在 github 中搜索 pytbox 项目,并参考我的配置。 Dockfile.dev 参考如下
# 使用官方 Python 3.11 镜像作为基础镜像 FROM python:3.11-slim # 设置工作目录 WORKDIR /app ENV PYTHONPATH=/app/automation \ TZ=Asia/Shanghai \ PIP_DISABLE_PIP_VERSION_CHECK=1 # 修改为国内源 COPY debian.sources /etc/apt/sources.list.d/debian.sources # 安装系统依赖 RUN apt-get update && apt-get install -y \ build-essential \ curl \ git \ && rm -rf /var/lib/apt/lists/*
创建完成后可以 Shift + Command/Alt + P ,选择 Dev Containers: Retbuild Container Without Cache 构建自己的开发环境,需要注意的是,下载镜像时间会比较久,需要耐心等待
准备 pyproject.toml 文件
pyproject.toml 已经取代了过去的 setup.py,成为 构建与依赖管理的统一入口。对于单仓库、多模块项目,它不仅定义了整个仓库的依赖,还能灵活指定每个子模块的配置。
[build-system] requires = ["setuptools>=61.0", "wheel"] build-backend = "setuptools.build_meta" [project] name = "myproject" version = "0.1.0" description = "A monorepo Python project" authors = [{ name = "YourName", email = "you@example.com" }] license = { text = "MIT" } readme = "README.md" requires-python = ">=3.8" dependencies = [ "requests>=2.0" ] [project.optional-dependencies] dev = ["pytest", "black", "ruff"]
开发环境下安装依赖可以执行 pip install .[dev] ,另外,如果模块依赖其他的包,例如 requests ,推荐手动写入到 dependencies 下
命令行工具
如果有命令行工具的需求,也就是说用户使用 pip install pytbox 后,希望执行 pytbox –help 使用命令行工具,可以按如下步骤操作 在 pyproject.toml 下加入
[project.scripts] pytbox = "pytbox.cli:main" # 添加命令行入口点
在项目根目录创建 exec.py 文件,作为入口
#!/usr/bin/env python3 """ 开发环境可执行文件 - 支持新的模块化 CLI """ import sys from pathlib import Path # 添加 src 目录到 Python 路径 src_path = Path(__file__).parent / 'src' if src_path.exists(): sys.path.insert(0, str(src_path)) from pytbox.cli import main if __name__ == "__main__": main()
在 src/pytbox 下创建 cli.py
#!/usr/bin/env python3 """ Pytbox 命令行工具 - 入口文件(保持向后兼容) """ from pytbox.cli import main if __name__ == "__main__": main()
然后在 src/pytbox/cli 目录下创建 main.py
#!/usr/bin/env python3 """ Pytbox 主命令行入口 """ import click from .categraf import categraf_group .group() .version_option() def main(): """Pytbox 命令行工具集合""" pass # 注册子命令组 main.add_command(categraf_group, name='categraf') if __name__ == "__main__": main()
在 src/pytbox/cli 目录下就可以创建多个目录,以实现各种这样的功能,例如我创建的 src/pytbox/cli/categraf 用于生成 categraf 的配置 示例文件 src/pytbox/cli/categraf/commands.py
""" Categraf 相关命令 - 支持 rich 美化输出 """ import shutil from pathlib import Path import click from ...utils.richutils import RichUtils from ...categraf.build_config import BuildConfig rich_utils = RichUtils() .group() def categraf_group(): """Categraf 配置管理工具""" pass .command('get-instances') .option('--output-dir', '-o', type=click.Path(exists=True), default='.') def get_instances(output_dir): """获取 Categraf 实例配置""" instances_template_path = Path(__file__).parent.parent.parent / 'categraf' / 'instances.toml' dest_path = Path(output_dir) / 'instances.toml' shutil.copy(instances_template_path, dest_path) rich_utils.print(msg=f'已将 {instances_template_path} 复制到 {dest_path}', style='info')
手动发布到 Pypi
准备 PYPI 的 API Token,这一步需要在 pypi 官网上配置
export TWINE_USERNAME="__token__" export TWINE_PASSWORD="pypi-xxxxxxxxxxxxxxxx"
安装依赖工具并发布
pip install build twine # 构建 python -m build # 发布到 PYPI twine upload dist/*
Github action 自动发布到 Pypi
强烈推荐使用 Action 自动发布,只要推送代码到 Github 就可以自动完成后续的步骤
-
在.github/workflows 目录下创建 publish.yml 文件
-
示例如下
namePublish to PyPI on push tags 'v*' jobs build runs-onubuntu-latest steps usesactions/checkout@v4 nameSet up Python usesactions/setup-python@v5 with python-version"3.11" nameInstall build tools runpip install build twine nameBuild package runpython -m build namePublish to PyPI env TWINE_USERNAME__token__ TWINE_PASSWORD$ secrets.PYPI_API_TOKEN runtwine upload dist/*
-
在仓库中的 Settings -> Secrets and variables -> Actions -> Repository secrets 下创建 name 为 PYPI_API_TOKEN 的 secret
-
推荐使用 shell 脚本 push 到仓库中,代码有些长,可以在 pytbox 库的根目录下参考
publish.sh
总结
在运维开发中,如果有多项目的需求,基本都是需要公共库的,否则需要被多项目调用的模块要重复编写,复制到其他项目非常不方便,并且难以保证代码质量,所以非常推荐编写自己或团队的公共代码库。
-