如何发布一个 Python 命令行工具

本文简介

上次写的一个终端里面斗鱼 TV 弹幕 Python 版本和 Ruby 版本,并且发布到 PIP 和 RubyGems 上面。在发布 PIP 包的时候,居然 Google 不到一篇可以非常好的讲解这个流程的文章。于是整理这篇文章,并且方便后来自己检索,并且方便他人找资料。

自推荐下依照本文定制的命令行工具 danmu.fm 的 github 地址:

https://github.com/twocucao/danmu.fm

本文的目的也是非常简单:

写一个 Python 命令行工具,并且发布到 PIP 上面.并且在这个过程中给出我自己的一些思考。

如何分解这个发布任务?

只需要进行如下的两个步骤便可以:

    1. 写好一个 Python 命令行工具。
    1. 发布它。

当然,这样不够细致。再细分一下。

    1. 写好一个 Python 命令行工具
    • 1.1. 命令行的特点,以及 Python 的如何编写命令行
    • 1.2. 如何组织代码结构。
    1. 发布
  • 2.1. 注册 pypi 账户
    • 2.2. 注册在账户下面注册 Python 包
    • 2.3. 上传打包好的 Python 命令行工具。
      1. 完善代码

1. 写好一个 Python 命令行工具

写好一个命令行工具首先要知道命令行工具是什么?

在我看来,命令行工具就是一种完成某种类型的任务的终端程序。

也就是基本上没有什么用户界面的程序。

由于基本上没有什么用户界面,所以导致单个命令行的交互能力及其低下。但这种低下的交互性对于一些固定工作而言,简直就是最灵活的工具。只需要输入一些命令便可以完成某种类型的工作。实在是方便的很。

所以,某种程度上,终端程序低交互的缺点反而成了优点。

1.1.Python 的如何编写一个简单的命令行

对于 Python 和命令行交互,我们很容易想出一个比较方便的方案。

sys.argv 就是这样的嘛!

我们很容易这样写代码。

1
python testargv.py thisisaargv1

甚至我们也可以这样写命令行,

1
python testargv.py thisisaargv1 -d -f 0

那么,这样写的后果就是,不方便解析出(不是不能,是不方便) -d -f 0 以及 thisisaargv1.

不信的话,你解析一个下面场景的命令行试试,

1
2
3
4
# 用户可能这样输入
danmu.fm http://www.douyutv.com/xiaocang -q 1 -v 2
danmu.fm -q 1 -v 2 http://www.douyutv.com/xiaocang
# 当然,肯定还有漏写啦,等等,你得需要转类型,增加各种 blablabla 的描述吧,添加默认的参数值吧。

于是 Python 就提供了一个非常好用的模块可以使用。叫做 argparse.

上面的描述就变成了这个样子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import argparse
APP_DESC="""
这就是描述
"""
print(APP_DESC)
if len(sys.argv) == 1:
sys.argv.append('--help')
parser = argparse.ArgumentParser()
parser.add_argument('-q','--quality',type=int,default=0,help="download video quality : 1 for the standard-definition; 3 for the super-definition")
parser.add_argument('-v','--verbose', default=0,help="print more debuging information")
parser.add_argument('-s','--store',help="保存流媒体文件到指定位置")
parser.add_argument('-c','--config',default=0,help="读取~/.danmu.fm 配置,请~/.danmu.fm 指定数据库")
parser.add_argument('url',metavar='URL',nargs='+', help="zhubo page URL (http://www.douyutv.com/*/)")
args = parser.parse_args()
# 获取对应参数只需要 args.quality,args.url 之类。
url = (args.url)[0]
print(url)
#其他执行逻辑

保存为 danmu.py

这样就可以执行命令

1
python danmu.py http://www.douyutv.com/xiaocang -q 1 -v 2

通过 args 就可以获取参数,然后进行终端程序的参数初始化。

可是这和我们的要求还是不同嘛,我们不想多写 Python XXX, 我们想直接 XXX. 就像这样。

1
danmu.fm -q 1 -v 2 http://www.douyutv.com/xiaocang

不急,下面就是了。

1.2. 如何组织代码结构。

于是,现在就要开始组织代码结构了。

我们在最终的代码目录大概是这样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
danmu.fm
├── README.md
├── danmufm
│   ├── __init__.py
│   ├── client
│   │   ├── __init__.py
│   │   ├── __init__.pyc
│   │   ├── douyu_client.py
│   │   └── douyu_danmu_client.py
│   ├── danmu.py
│   ├── misc
│   │   ├── __init__.py
│   │   ├── color_printer.py
│   │   ├── downloaders.py
│   │   └── player.py
│   └── model
│   ├── __init__.py
│   └── douyu_msg.py
├── docs
├── setup.cfg
├── setup.py
├── sh.py
└── tests

这就是我上次写的 danmu.fm 的代码目录。

聪明的你这时候你注意到了:

  1. 主要的程序不是放在根目录下面,而是放在第二目录 danmufm 下面。
    2.setup.cfg 是什么鬼东西
    3.setup.py 是什么鬼东西

对于上面几点,我们分别进行解释

###1.2.1 为什么主要程序在第二目录下

为了把主要的程序分离出来,放在第二目录下面,这样的待会打包以后多出很多文件夹就不会对源码造成干扰。

当然,由于把程序放在了第二目录下面,所以,脚本里面的 from import 语句应该使用相对路径导入。

相对路径导入的的时候需要注意运行的时候使用如下命令

1
python3 -m danmufm.danmu [xxxx]

###1.2.2 setup.cfg

填写如下内容即可。

1
2
[metadata]
description-file = README.md

然后去写 Markdown 的 Readme 就好了。

###1.2.3 setup.py

这个是重头戏了。

setup 这个 py 文件就是打包配置文件。对这个程序是谁的,有什么依赖,入口是什么,等等等等的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#-*- encoding: UTF-8 -*-
from setuptools import setup, find_packages
"""
打包的用的 setup 必须引入,
"""
VERSION = '0.1.1'
setup(name='danmu.fm',
version=VERSION,
description="a tiny and smart cli player of douyutv,ximalayad,anmu based on Python",
long_description='just enjoy',
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
keywords='python douyu danmu danmu.fm terminal',
author='twocucao',
author_email='twocucao@gmail.com',
url='https://github.com/twocucao/doumu.fm',
license='MIT',
packages=find_packages(),
include_package_data=True,
zip_safe=True,
install_requires=[
'requests',
],
entry_points={
'console_scripts':[
'danmu.fm = danmufm.danmu:main'
]
},
)

官方有 distutils 这个包管理器工具,设置也非常的简单,只是,它不支持 entry_points 属性,由于无法使用 entry_point, 也就无法通过命令来跳转到指定模块运行程序,这也就意味着,官方工具不方便写成命令行。还是 setuptools 好。

上面需要注意的就是 install_requires 可以添加依赖。其他的你猜都可以猜出来是做什么的。自己去看代码,我就不多说了。

2. 发布

所谓的发布,就是将打包好的程序的某个版本发布到某个仓库中。

2.1. 注册 pypi 账户

到这个上面注册账号:
https://pypi.python.org/pypi

2.2. 注册在账户下面注册 Python 包

进入对应项目根文件,然后执行

1
python3 setup.py register

这一步程序会让你输入刚刚注册的账号和密码,然后注册该包。注册该包以后,你就有了一个小仓库。可以存放不同版本的 danmu.fm.

注册的仓库是可以在这个地址看到的,
https://pypi.python.org/pypi?%3Aaction=pkg_edit&name=danmu.fm

2.3. 上传打包好的 Python 命令行工具。

这里需要借助一个小工具,twine.twine 是一个更加安全方便上传打包好的代码的工具。

1
pip3 install twine

接着开始打包,打包成两个版本,一个是不需要 build 的版本,另一个是需要 build 的版本(顺带吐槽下,这两个诡异的命名).

1
python setup.py sdist bdist_wheel

于是剩下来的就显而易见了,上传 build 完毕的程序到仓库中。

1
twine upload dist/danmu.fm-0.1.2*

于是,安装一下,测试是否成功

1
pip3 install danmu.fm --upgrade

命令行的工具是这样使用的。

1
danmu.fm -q 2 -v 1 http://www.douyutv.com/16789

3. 完善

不断的完善代码,然后打包终端程序发布到仓库给别人用,这就是整个的 PIP 打包发布流程。

  • 这个时候,你可能需要使用版本控制软件。
  • 你可能需要增多的代码的测试。