跳至主要内容

新的预命令和后命令插件钩子

·阅读时长 4 分钟
Travis Hathaway
Conda 维护者 👷🔧

随着最新的 conda 版本发布(在撰写本文时为 23.7.2),引入了定义两个新插件钩子的功能:“预命令”和“后命令”。这两个新的插件钩子让插件作者能够在 conda 命令运行之前和之后执行代码。在这篇博文中,我们将更详细地介绍如何以及为什么可以使用它们来扩展 conda 的默认行为。

信息

有关如何在实践中使用这些插件钩子的完整功能示例,请查看 conda-protect 项目。


为了解释如何使用这些插件钩子,我们将介绍两个示例。

Conda protect 和“预命令”钩子

上面链接的项目称为 conda-protect,它通过添加功能来扩展 conda,以“保护”conda 环境免受更改。有时用户可能希望这样做,以保护自己免受 无意修改其基本环境

使用“预命令”插件钩子,我们首先可以指定要保护的命令,然后运行在执行命令之前调用的函数。通过这样做,我们可以找出环境是否受到保护,如果受到保护,则尽早退出。以下是此类插件钩子的定义方式

from conda import plugins

def conda_protect_pre_commands_action(command: str) -> None:
"""Checks to see if the current environment is protected"""
environment = get_current_environment()

if is_guarded(environment):
raise CondaError(
f"Current environment is protect. Run `conda guard {environment}` to "
"remove protection."
)

@plugins.hookimpl
def conda_pre_commands():
yield plugins.CondaPreCommand(
name=f"conda_protect_pre_command",
action=conda_protect_pre_commands_action,
run_for={"install", "remove", "update", "env_update", "env_remove"},
)

在上面的示例中,我们首先通过定义名为 conda_pre_commands 的函数来注册我们的插件钩子,然后使用 conda 提供的 hookimpl 函数对其进行装饰。在钩子函数本身内部,我们返回一个 CondaPreCommand 对象,它定义了以下内容

  • name:此插件钩子在 conda 内部将如何引用
  • action:在 run_for 中定义的命令执行之前将调用的可调用对象
  • run_for:此插件钩子应与之一起使用的命令的 set

回到我们的示例,如果我们想要创建一个插件,该插件可以防止我们错误地修改受保护的环境,那么将 run_for 定义为所有可能修改环境的 conda 命令(例如 installremove 等)是有意义的。

为了实际防止我们修改受保护的环境,action 函数会检查当前环境是否受到保护,然后如果受到保护,则抛出 CondaError,以确保程序尽早退出,并向用户显示有意义的错误消息。

信息

有关完整实现,请参阅 conda-protect 项目。

使用“后命令”钩子的简单命令计数器

“后命令”钩子的功能与“预命令”完全相同,只是它在 conda 命令成功完成运行后被调用。需要注意的是,如果命令由于任何原因过早退出,则不会调用此插件钩子。

为了说明如何使用它,我们将创建一个简单的插件来计算运行特定 conda 命令的次数。我们以后可以使用它来分析我们对 conda 的使用情况 🤓 📊

以下是如何使该插件看起来的代码片段

from conda.cli.conda_argparse import BUILTIN_COMMANDS
from conda import plugins

ENV_COMMANDS = {
"env_config", "env_create",
"env_export", "env_list",
"env_remove", "env_update"
}

def conda_stats_post_commands_action(command: str):
"""Counts how many times we have run a particular conda command"""
database = get_database()
database.add_command(command)

@plugins.hookimpl
def conda_post_commands():
yield plugins.CondaPostCommand(
name=f"conda_stats_post_command",
action=conda_stats_post_commands_action,
run_for=BUILTIN_COMMANDS.union(ENV_COMMANDS),
)

上面的示例通过定义名为 conda_post_commands 的函数来注册我们的“后命令”钩子。与“预命令”钩子非常相似,它返回一个 CondaPostCommand 对象,该对象定义了 nameactionrun_foraction 函数在每次运行命令时计算命令的使用次数,并且 run_for 属性被配置为针对每个内置 conda 命令运行,包括 conda env * 命令。

注意

数据库的实现故意从示例中省略,因为它超出了这篇博文的范围(自己动手实现它,很有趣!😅)。

总结

如果你想开始使用这些新的插件,请查看 conda-protect 项目。此项目也可以用作插件的起始模板。

有关 conda 目前可用的所有插件钩子的更多信息,请访问 相关文档页面

与往常一样,欢迎您访问我们的 Matrix 聊天室,提出任何问题或提供反馈。

祝您编码愉快 ✌️