构建包
当您使用 **Typer** 创建 CLI 程序时,您可能希望创建自己的 Python 包。
这允许您的用户安装它,并将其作为独立程序在终端中使用。
这也是 shell 自动完成工作所必需的(除非您通过 typer
命令使用您的程序)。
如今,有几种方法和工具可以创建 Python 包(您使用 pip install something
安装的内容)。
您可能已经有了自己喜欢的。
这是一个非常主观的简短指南,展示了从头开始使用 **Typer** 应用程序创建 Python 包的另一种方法。
提示
如果您已经有了自己喜欢的创建 Python 包的方法,可以随意跳过此步骤。
先决条件¶
在本指南中,我们将使用 Poetry。
Poetry 的文档非常棒,所以请查看并安装它。
创建项目¶
假设我们要创建一个名为 portal-gun
的 CLI 应用程序。
为了确保您的包不会与其他人创建的包冲突,我们将使用您的姓名作为前缀。
因此,如果您的名字是 Rick,我们将将其命名为 rick-portal-gun
。
使用 Poetry 创建项目
$ poetry new rick-portal-gun
Created package rick_portal_gun in rick-portal-gun
// Enter the new project directory
cd ./rick-portal-gun
依赖项和环境¶
将 typer[all]
添加到您的依赖项中
$ poetry add "typer[all]"
// It creates a virtual environment for your project
Creating virtualenv rick-portal-gun-w31dJa0b-py3.10 in /home/rick/.cache/pypoetry/virtualenvs
Using version ^0.1.0 for typer
Updating dependencies
Resolving dependencies... (1.2s)
Writing lock file
---> 100%
Package operations: 15 installs, 0 updates, 0 removals
- Installing zipp (3.1.0)
- Installing importlib-metadata (1.5.0)
- Installing pyparsing (2.4.6)
- Installing six (1.14.0)
- Installing attrs (19.3.0)
- Installing click (7.1.1)
- Installing colorama (0.4.3)
- Installing more-itertools (8.2.0)
- Installing packaging (20.3)
- Installing pluggy (0.13.1)
- Installing py (1.8.1)
- Installing shellingham (1.3.2)
- Installing wcwidth (0.1.8)
- Installing pytest (5.4.1)
- Installing typer (0.0.11)
// Activate that new virtual environment
$ poetry shell
Spawning shell within /home/rick/.cache/pypoetry/virtualenvs/rick-portal-gun-w31dJa0b-py3.10
// Open an editor using this new environment, for example VS Code
$ code ./
您可以看到,您已经生成了一个看起来像这样的项目结构
.
├── poetry.lock
├── pyproject.toml
├── README.md
├── rick_portal_gun
│ └── __init__.py
└── tests
├── __init__.py
└── test_rick_portal_gun.py
创建您的应用程序¶
现在让我们创建一个非常简单的 Typer 应用程序。
创建一个名为 rick_portal_gun/main.py
的文件,其中包含
import typer
app = typer.Typer()
@app.callback()
def callback():
"""
Awesome Portal Gun
"""
@app.command()
def shoot():
"""
Shoot the portal gun
"""
typer.echo("Shooting portal gun")
@app.command()
def load():
"""
Load the portal gun
"""
typer.echo("Loading portal gun")
提示
由于我们正在创建一个可安装的 Python 包,因此无需添加包含 if __name__ == "__main__":
的部分。
修改 README¶
让我们更改 README,使其包含以下内容
# Portal Gun
The awesome Portal Gun
添加一个“脚本”¶
我们正在创建一个可以使用 pip install
安装的 Python 包。
但我们希望它提供一个可以在 shell 中执行的 CLI 程序。
为此,我们在 pyproject.toml
中的 [tool.poetry.scripts]
部分添加一个配置
[tool.poetry]
name = "rick-portal-gun"
version = "0.1.0"
description = ""
authors = ["Rick Sanchez <rick@example.com>"]
readme = "README.md"
[tool.poetry.scripts]
rick-portal-gun = "rick_portal_gun.main:app"
[tool.poetry.dependencies]
python = "^3.10"
typer = {extras = ["all"], version = "^0.1.0"}
[tool.poetry.dev-dependencies]
pytest = "^5.2"
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
以下是该行的含义
rick-portal-gun
:将是 CLI 程序的名称。这就是我们在安装后在终端中调用它的方式。例如
$ rick-portal-gun
// Something happens here ✨
rick_portal_gun.main
,在 "rick_portal_gun.main:app"
部分中,使用下划线,指的是要导入的 Python 模块。这就是人们在类似以下部分中使用它的方式
from rick_portal_gun.main import # something goes here
"rick_portal_gun.main:app"
中的 app
是要从模块中导入并作为函数调用的内容,例如
from rick_portal_gun.main import app
app()
该配置部分告诉 Poetry,当安装此包时,我们希望它创建一个名为 rick-portal-gun
的命令行程序。
并且要调用的对象(例如函数)是 rick_portal_gun.main
模块中变量 app
中的对象。
安装您的包¶
这就是我们创建包所需的。
现在你可以安装它了。
$ poetry install
Installing dependencies from lock file
No dependencies to install or update
- Installing rick-portal-gun (0.1.0)
尝试你的 CLI 程序¶
你的包安装在 Poetry 创建的环境中,但你已经可以使用它了。
// You can use the which program to check which rick-portal-gun program is available (if any)
$ which rick-portal-gun
// You get the one from your environment
/home/rick/.cache/pypoetry/virtualenvs/rick-portal-gun-w31dJa0b-py3.10/bin/rick-portal-gun
// Try it
$ rick-portal-gun
// You get all the standard help
Usage: rick-portal-gun [OPTIONS] COMMAND [ARGS]...
Awesome Portal Gun
Options:
--install-completion Install completion for the current shell.
--show-completion Show completion for the current shell, to copy it or customize the installation.
--help Show this message and exit.
Commands:
load Load the portal gun
shoot Shoot the portal gun
创建轮子包¶
Python 包有一个标准格式,称为“轮子”。它是一个以 .whl
结尾的文件。
你可以使用 Poetry 创建轮子。
$ poetry build
Building rick-portal-gun (0.1.0)
- Building sdist
- Built rick-portal-gun-0.1.0.tar.gz
- Building wheel
- Built rick_portal_gun-0.1.0-py3-none-any.whl
之后,如果你检查你的项目目录,你应该会在 ./dist/
中看到一些额外的文件。
.
├── dist
│ ├── rick_portal_gun-0.1.0-py3-none-any.whl
│ └── rick-portal-gun-0.1.0.tar.gz
├── pyproject.toml
├── README.md
├── ...
.whl
是轮子文件。你可以将这个轮子文件发送给任何人,他们可以使用它来安装你的程序(我们将在稍后看到如何将其上传到 PyPI)。
测试你的轮子包¶
现在你可以打开另一个终端,并使用以下命令从文件中为你的用户安装该包:
$ pip install --user /home/rock/code/rick-portal-gun/dist/rick_portal_gun-0.1.0-py3-none-any.whl
---> 100%
警告
--user
很重要,它确保你将它安装在你的用户目录中,而不是在全局系统中。
如果你将其安装在全局系统中(例如,使用 sudo
),你可能会安装一个与你的系统不兼容的库版本(例如,子依赖项)。
提示
如果你使用 pipx
来安装它,同时为你的 Python CLI 程序保持一个隔离的环境,那么你将获得额外的奖励 🚀
现在你已经安装了你的 CLI 程序。你可以自由使用它。
$ rick-portal-gun shoot
// It works 🎉
Shooting portal gun
将其安装在全局(而不是单个环境中),你现在可以为它全局安装完成。
$ rick-portal-gun --install-completion
zsh completion installed in /home/user/.zshrc.
Completion will take effect once you restart the terminal.
提示
如果你想删除完成,你只需删除该文件中添加的行。
重新启动终端后,你将获得新 CLI 程序的完成。
$ rick-portal-gun [TAB][TAB]
// You get completion for your CLI program ✨
load -- Load the portal gun
shoot -- Shoot the portal gun
支持 python -m
(可选)¶
你可能已经注意到,你可以使用 python -m some-module
将许多 Python 模块作为脚本调用。
例如,调用 pip
的一种方法是
$ pip install fastapi
但你也可以使用 -m
CLI 选项 调用 Python,并传递一个模块,让它像脚本一样执行,例如
$ python -m pip install fastapi
这里我们将 pip
作为 -m
的值传递,因此 Python 将执行模块 pip
,就像它是一个脚本一样。然后它将把其余的 CLI 参数(install fastapi
)传递给它。
这两个命令基本等效,install fastapi
会传递给 pip
。
提示
在使用 pip
的情况下,很多时候建议使用 python -m
来运行它,因为如果你创建了一个带有自己 python
的虚拟环境,这将确保你使用的是该环境中的 pip
。
添加 __main__.py
¶
你可以通过添加一个名为 __main__.py
的文件来支持对你的包/模块的相同调用方式。
Python 会查找该文件并执行它。
该文件应该与 __init__.py
位于同一目录下。
.
├── poetry.lock
├── pyproject.toml
├── README.md
├── rick_portal_gun
│ ├── __init__.py
│ ├── __main__.py
│ └── main.py
└── tests
├── __init__.py
└── test_rick_portal_gun.py
其他文件不需要导入它,你也不需要在你的 pyproject.toml
或其他任何地方引用它,它默认情况下就可以工作,因为这是 Python 的标准行为。
然后,你可以在该文件中执行你的 Typer 程序。
from .main import app
app()
现在,在你安装完你的包后,如果你使用 python -m
调用它,它将正常工作(对于主要部分)。
$ python -m rick_portal_gun
Usage: __main__.py [OPTIONS] COMMAND [ARGS]...
Awesome Portal Gun
Options:
--install-completion Install completion for the current shell.
--show-completion Show completion for the current shell, to copy it or customize the installation.
--help Show this message and exit.
Commands:
load Load the portal gun
shoot Shoot the portal gun
提示
请注意,你必须传递包名的可导入版本,所以是 rick_portal_gun
而不是 rick-portal-gun
。
它可以工作!🚀 有点... 🤔
在帮助信息中看到 __main__.py
而不是 rick-portal-gun
了吗?我们将在下一步解决这个问题。
在 __main__.py
中设置程序名称¶
我们在 pyproject.toml
文件中设置程序名称,类似于以下行:
[tool.poetry.scripts]
rick-portal-gun = "rick_portal_gun.main:app"
但是当 Python 使用 python -m
运行我们的包作为脚本时,它没有程序名称的信息。
因此,为了修复帮助文本,使其在使用 python -m
调用时使用正确的程序名称,我们可以将其传递给 __main__.py
中的应用程序。
from .main import app
app(prog_name="rick-portal-gun")
提示
你可以传递所有可以传递给 Click 应用程序的参数和关键字参数,包括 prog_name
。
$ python -m rick_portal_gun
Usage: rick-portal-gun [OPTIONS] COMMAND [ARGS]...
Awesome Portal Gun
Options:
--install-completion Install completion for the current shell.
--show-completion Show completion for the current shell, to copy it or customize the installation.
--help Show this message and exit.
Commands:
load Load the portal gun
shoot Shoot the portal gun
太棒了!它可以正常工作了!🎉 ✅
请注意,现在它在帮助信息中使用 rick-portal-gun
而不是 __main__.py
。
自动完成和 python -m
¶
请记住,使用 python -m
时,TAB 补全(shell 自动补全)将无法工作。
自动补全依赖于调用的程序名称,它与每个特定的程序名称绑定。
因此,要为 rick-portal-gun
提供 shell 补全,你必须直接调用它。
$ rick-portal-gun [TAB][TAB]
但你仍然可以支持 python -m
,以便在它有用的情况下使用它。
发布到 PyPI(可选)¶
您可以将新包发布到 PyPI 以使其公开,以便其他人可以轻松安装它。
因此,请立即创建一个帐户(它是免费的)。
PyPI API 令牌¶
为此,您首先需要配置一个 PyPI 身份验证令牌。
登录到 PyPI。
然后转到 https://pypi.ac.cn/manage/account/token/ 创建一个新令牌。
假设您的新 API 令牌是
pypi-wubalubadubdub-deadbeef1234
现在使用命令 poetry config pypi-token.pypi
配置 Poetry 以使用此令牌。
$ poetry config pypi-token.pypi pypi-wubalubadubdub-deadbeef1234
// It won't show any output, but it's already configured
发布到 PyPI¶
现在您可以使用 Poetry 发布您的包。
您可以构建包(如上所示),然后稍后发布,或者您可以告诉 Poetry 在一次操作中构建它然后再发布。
$ poetry publish --build
# There are 2 files ready for publishing. Build anyway? (yes/no) [no] $ yes
---> 100%
Building rick-portal-gun (0.1.0)
- Building sdist
- Built rick-portal-gun-0.1.0.tar.gz
- Building wheel
- Built rick_portal_gun-0.1.0-py3-none-any.whl
Publishing rick-portal-gun (0.1.0) to PyPI
- Uploading rick-portal-gun-0.1.0.tar.gz 100%
- Uploading rick_portal_gun-0.1.0-py3-none-any.whl 100%
现在您可以转到 PyPI 并查看您的项目,地址为 https://pypi.ac.cn/manage/projects/。
您现在应该看到您的新“rick-portal-gun”包。
从 PyPI 安装¶
现在,要查看我们是否可以从 PyPI 安装它,请打开另一个终端并卸载当前安装的包。
$ pip uninstall rick-portal-gun
Found existing installation: rick-portal-gun 0.1.0
Uninstalling rick-portal-gun-0.1.0:
Would remove:
/home/user/.local/bin/rick-portal-gun
/home/user/.local/lib/python3.10/site-packages/rick_portal_gun-0.1.0.dist-info/*
/home/user/.local/lib/python3.10/site-packages/rick_portal_gun/*
# Proceed (y/n)? $ y
Successfully uninstalled rick-portal-gun-0.1.0
现在再次安装它,但这次只使用名称,以便 pip
从 PyPI 获取它。
$ pip install --user rick-portal-gun
// Notice that it says "Downloading" 🚀
Collecting rick-portal-gun
Downloading rick_portal_gun-0.1.0-py3-none-any.whl (1.8 kB)
Requirement already satisfied: typer[all]<0.0.12,>=0.0.11 in ./.local/lib/python3.10/site-packages (from rick-portal-gun) (0.0.11)
Requirement already satisfied: click<7.2.0,>=7.1.1 in ./anaconda3/lib/python3.10/site-packages (from typer[all]<0.0.12,>=0.0.11->rick-portal-gun) (7.1.1)
Requirement already satisfied: colorama; extra == "all" in ./anaconda3/lib/python3.10/site-packages (from typer[all]<0.0.12,>=0.0.11->rick-portal-gun) (0.4.3)
Requirement already satisfied: shellingham; extra == "all" in ./anaconda3/lib/python3.10/site-packages (from typer[all]<0.0.12,>=0.0.11->rick-portal-gun) (1.3.1)
Installing collected packages: rick-portal-gun
Successfully installed rick-portal-gun-0.1.0
现在测试从 PyPI 新安装的包。
$ rick-portal-gun load
// It works! 🎉
Loading portal gun
生成文档¶
您可以使用 typer
命令为您的包生成文档,您可以将其放在您的 README.md
中。
$ typer rick_portal_gun.main utils docs --output README.md --name rick-portal-gun
Docs saved to: README.md
您只需要将要导入的模块(rick_portal_gun.main
)传递给它,它将自动检测 typer.Typer
应用程序。
通过指定程序的 --name
,它将能够在生成文档时使用它。
提示
如果您安装了 typer-slim
并且没有 typer
命令,则可以使用 python -m typer
代替。
使用文档发布新版本¶
现在您可以使用更新的文档发布新版本。
为此,您首先需要在 pyproject.toml
中增加版本。
[tool.poetry]
name = "rick-portal-gun"
version = "0.2.0"
description = ""
authors = ["Rick Sanchez <rick@example.com>"]
readme = "README.md"
[tool.poetry.scripts]
rick-portal-gun = "rick_portal_gun.main:app"
[tool.poetry.dependencies]
python = "^3.10"
typer = {extras = ["all"], version = "^0.1.0"}
[tool.poetry.dev-dependencies]
pytest = "^5.2"
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
在文件 rick_portal_gun/__init__.py
中
__version__ = '0.2.0'
然后再次构建和发布
$ poetry publish --build
---> 100%
Building rick-portal-gun (0.2.0)
- Building sdist
- Built rick-portal-gun-0.2.0.tar.gz
- Building wheel
- Built rick_portal_gun-0.2.0-py3-none-any.whl
Publishing rick-portal-gun (0.2.0) to PyPI
- Uploading rick-portal-gun-0.2.0.tar.gz 100%
- Uploading rick_portal_gun-0.2.0-py3-none-any.whl 100%
现在你可以去 PyPI,到项目页面,重新加载它,它现在将包含你新生成的文档。
下一步¶
这是一个非常简单的指南。你可以添加更多步骤。
例如,你应该使用 Git,版本控制系统,来保存你的代码。
你可以在你的 pyproject.toml
中添加很多额外的元数据,查看 Poetry: Libraries 的文档。
你可以使用 pipx
在隔离的环境中管理你安装的 CLI Python 程序。
也许可以使用 Black 进行自动格式化。
你可能希望将你的代码作为开源代码发布到 GitHub 上。
然后你可以集成一个 CI 工具来自动运行你的测试并部署你的包。
等等等等。但现在你已经掌握了基础知识,你可以继续自己探索 🚀。