type
Post
status
Published
date
slug
summary
通过PyPI将自己的包进行发布,之后可以方便通过pip进行安装使用
tags
工具
category
高效工具
icon
password
Property
Sep 19, 2022 03:44 PM

Introduction

为了使自己开源的python库或者应用使用起来更加方便,所以计划将其打包并托管至PyPI。托管很简单,主要的工作聚集在如何对项目进行打包和分发
python的打包、分发工具演进历史比较复杂,目前被大家公认的仅有setuptools,其仍在持续更新,本文介绍本着最佳实践的理念将仅介绍如何基于setuptools进行打包和发布
setuptools的使用需要依赖工程中的 setup.py 、setup.cfg、MANIFEST.in等配置文件进行正确配置后才可奏效,这也是在开源工程中常见的几个文件,其具体的配置规则在其官方文档中有详细的说明
💡 个人观点:上述的几个文件中,setup.py是setuptools入口,采用python语法编写,很便于理解,但也因为python语法使得其没有一个明确的规范,很多项目为了达到一些个性化或者自动化的配置,在其中编写了过多的辅助函数,一个setup.py文件能有上百行代码,这在很多开源项目中可见一斑。为了改观该现象,setuptools项目组建议使用setup.cfg作为配置文件,在其中按照指定规则填写参数,然后传入setup.py中,并且他们在setup.cfg中添加了很多人性化的支持,如:根据git 仓库版本号填充打包的版本号、文档内容控制等等,并且他们宣称可以在setup.py中配置的,在setup.cfg中都可以配置,本按理会出现setup.cfg一统配置江湖的场景,但是其文档自2016年起至今,setup.py的使用依然乱象,个人觉得根本原因还是setup.cfg文件的配置太难写了。在参考众多优秀开源项目后,个人决定,项目的setuptools配置依然使用setup.py来完成,在配置的时候仅需要注意维护,但是写起来会非常便捷。下面将详细的介绍如何写一个setup.py并满足各式各样的需求。此外他们还有计划使用toml格式配置文件来达到极简配置的目的,但是没什么人用好像,也就不在此赘述了。

How to write a setup.py

将以个人项目wadda作为实例,来详细说明如何编写一个setup.py。wadda不仅仅包含可执行文件,也包含库文件,其将会是一个绝好的模板例子 😊

wadda introduction

💡:因为wadda仍在持续的开发中,所以当你看到这篇文章的时候,其实际内容可能会与本文有些许差异。但个人会尽量保持文章的更新以修正差异的
wadda是一个集成了个人在自动驾驶相关开发工作中的常用的一些小工具和库,例如查看pcd文件、将voc格式数据集转换为coco、录制数据、ros消息格式转换等等。所以其同时包含可执行CML,也包括一些库

工程结构

wadda ├── docs # 文档 │ ├── en │ ├── requirements.txt │ └── zh_cn ├── LICENSE ├── MANIFEST.in ├── README.md ├── requirements # 安装依赖,因为功能比较杂,所以将不同功能包的依赖分开写 │ └── pypcd.txt ├── setup.py # 需要配置的主角 ├── wadda # 工程的src,存放所有源码相关的内容。如果你喜欢也可以叫src │ ├── main.py │ ├── pypcd # 个人魔改过的一个pypcd库,使其兼容python3 │ └── ros_visualizer # ros消息可视化工具 └── wadda.py

basic

以下是一个基础的配置情况,应该可以满足绝大多数的情况,可以作为基础的参考模板
from setuptools import setup, find_packages def parse_requirements(fname_list=[]): """Parse the package dependencies listed in a requirements list file.""" requirements = [] for fname in fname_list: with open(fname) as f: for line in f: line = line.strip() if line and not line.startswith("#"): requirements.append(line) # remove duplicates requirements = list(set(requirements)) return requirements # basic setup( # 描述信息 name="wadda", # 项目名 version="0.0.1", # 版本号 description="Wind's Autonomous Driving Development Art", author="windzu", author_email="windzu1@gmail.com", url="https://github.com/windzu/wadda", license="MIT license", keywords="adas deeplearning", # 相关分类描述,3表示Alpha阶段 # 详情可参考https://pypi.org/pypi?%3Aaction=list_classifiers classifiers=[ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", ], # 主要设置 python_requires=">=3.6", # python版本指定 packages=find_packages(exclude=("docs")), install_requires=parse_requirements( [ "requirements/requirements.txt", "requirements/pypcd.txt", ] ), entry_points={"console_scripts": ["wadda=wadda.main:main"]}, # 次要设置 include_package_data=True, zip_safe=False, )

advanced

为了满足更多个性化的配置,还有一些额外的选项可以配置

打包/上传时的非py静态文件控制

类似于上传github时的.gitignore的设置,在通过setuptools进行打包的时候可以控制哪些被打包,具体实现的方法有2种,一种是在setup.py中配置,一种是通过MANIFEST.in文件配置,个人更倾向于后者,这样和git的使用很像,符合习惯。
⚠️ 注意 :如果启用MANIFEST.in的支持,include_package_data参数一定要设置为True
  • incude : 指定具体的文件名,支持正则匹配
  • recursive-include:指定一个文件夹下,设置条件,递归查找并指定
  • prune:指定一个文件夹,忽略其所以内容
include README.md # README希望被包含 include requirements/*txt # 所有的requirement文件希望被包含

本地安装测试

pip3 install -e .# e为开发模式,修改工程后不用重新再次安装便可奏效

发布至PyPI

在完成本地打包测试后,发布的工作就很简单了。需要准备好PyPI的帐号,以及为自己的包起一个独一无二的名字(最难的工作 🥲)

发布至PyPi测试环境

注册帐号

注册地址为:https://test.pypi.org/account/register/,需要邮箱验证

打包发布

通过官方推荐的twine工具进行发布
# 如果没安装twine先安装一下 pip3 install twine # 1. 打包至dist文件夹中,成功后将在根目录出现dist文件夹,其中包含tar.gz 和 whl两个文件 python3 setup.py sdist bdist_wheel # 2. 发布至testpypi,然后根据指示输入刚才注册帐号和密码即可 python3 -m twine upload --repository testpypi dist/*

发布至PyPi真实环境

与测试环境几乎完全一样的操作流程,唯一不同的是不需要指定 testpypi
python3 -m twine upload dist/*

下载测试

pip3 install wadda

Github Action

虽然已经完成了全部需求,但是每次更新还得自己进行本地推送实在不够优雅,这里通过github action完成自动部署
我希望的工作流程
  1. 个人在本地dev分支完成开发和测试后,推送至github
  1. 如果是有新特性更新或者是有bug修复,则基于最新的dev新建一个分支,用于merge至main
  1. merge完成触发github action,完成自动的推送PyPI的工作

申请token

申请pypi token,并在github对应的仓库中将其设置为secrets,如果担心操作错误也可以先仿照之前发布PyPI时候一样,先用test pypi测试
  • PyPI TOKEN / Test PyPI TOKEN 申请:account settings 底部(token只会出现一次,未保存之前请勿关闭页面)
  • Github Secrets添加:仓库-setting-secrets-actions 右上角 New Repositry secret,名称为PYPI_TOKEN

github action脚本

该脚本会在其他branch merge to main 时候触发
💡 注意 : 如果想要进行新版本发布,即merge to main,记得先修改setup.py中的版本号,否则会版本号冲突导致publish失败的
name: Publish to test PyPI on: pull_request: # pr to main will trigger action branches: - main jobs: build: name: Build and publish to test PyPI # Name of the job runs-on: ubuntu-latest # set the OS steps: - uses: actions/checkout@v3 # Action to checkout the code - name: Set up Python 3.8 # Name of the step uses: actions/setup-python@v3 # Action to setup Python with: python-version: 3.8 # Python version to use - name: Install dependencies run: | # install the dependencies python -m pip install --upgrade pip pip install setuptools wheel twine - name: Build and publish env: # set the environment variables TWINE_USERNAME: __token__ # default __token__ TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} run: | # build and publish the package python setup.py sdist bdist_wheel twine upload dist/*

Reference Table

为了方便后续的查找使用,将常用配置项记录下来,其内容整理主要参考了这篇文章,且将其分为了几个部分

描述信息

参数
说明
name
包名称
version
包版本
author
程序的作者
author_email
程序的作者的邮箱地址
maintainer
维护者
maintainer_email
维护者的邮箱地址
url
程序的官网或文档地址
License
程序的版权信息
description
程序的简单描述,一句话描述即可
long description
程序的详细描述,常见做法是导入README或者是doc的内容
platforms
程序适用的软件平台列表
classifiers
程序的所属分类列表
keywords
程序的关键字列表
download_url
程序的下载地址

主要设置参数

参数
说明
packages
指定需要打包的包目录(这些包中通常为包含__init__.py的文件夹),一般通过find_packages() 自动搜寻,再配合exclude参数排除不希望打包的包
install_requires
指定所打包的这些包,在安装时它们需要的依赖,最好具体到版本
entry_points
动态发现服务和插件的入口。一般用于:希望将代码打包为命令行可执行执行指令时使用,指向程序的main函数入口即可
scripts
指定可执行脚本,安装时脚本会被安装到系统PATH路径下

默认需要设置的参数

参数
说明
include_package_data
自动包含包内所有受版本控制(cvs/svn/git)的数据文件,一般设置为True
zip_safe
是否压缩包,如不压缩则以目录的形式安装,一般设置为False
python_requires
强制指定python的版本,但是现在已经进入python3时代了,除非是使用了比较新的特性可能会需要指定,例如使用了python3.9某特性,一般设置为">=3.6”即可

默认不需要设置(不启用)的参数

参数
说明
package_dir
为某个指定路径设置一个名称,设置的名称即为包名,在后续设置中可以通过该包名来指代该路径,常见的用法是将包的根目录设置一个名称,但是如果不设置的话默认从根路径开始即可(所以觉得没有设置的必要)

可被MANIFEST.in取代的用于分发控制的参数

参数
说明
data_files
设置打包时需要打包的非py的数据文件,如图片,配置文件等
package_data
指定包内必须要包含的数据文件。一般用于强制指定打包时必须要有的配置文件或者数据文件
exclude_package_data
当include_package_data 为True 时该选项用于排除部分文件

setup拓展设置参数

参数
说明
setup_requires
setup.py 本身要依赖的包,这通常是为一些setuptools的插件准备的配置
ext_modules
指定扩展模块,参数用于构建 C 和 C++ 扩展扩展包

尚未归类

参数
说明
pymodules
需要打包的Python单文件列表
cmdclass
添加自定义命令
requires
指定依赖的其他包
provides
指定可以为哪些模块提供依赖
dependency_links
指定依赖包的下载地址
extras_require
当前包的高级/额外特性要依赖的分发包

参考链接

  1. 官方打包的教程:https://packaging.python.org/en/latest/tutorials/packaging-projects/
  1. 详解setup.py的编写规则:https://mp.weixin.qq.com/s/rNcrLbPSkBKfw3VNr9XYnA?spm=a2c6h.12873639.article-detail.13.5755fd8duzkXk0
  1. 打包发布教程:https://blog.csdn.net/yifengchaoran/article/details/113447773
 
Sphinx使用优化Shell