跳到主要内容

新的前置和后置命令插件钩子

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

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

信息

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


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

Conda protect 和 “pre command” 钩子

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

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

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 项目。

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

“post command” 钩子的功能与 “pre command” 完全相同,只是它在 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 的函数来注册我们的 “post command” 钩子。与 “pre command” 钩子非常相似,它返回一个 CondaPostCommand 对象,该对象定义了 nameactionrun_foraction 函数计算每次命令运行时命令的使用次数,run_for 属性配置为对每个内置 conda 命令运行,包括 conda env * 命令。

注意

数据库的实现已特意从示例中省略,因为它超出了本博文的范围(自己实现它来获得乐趣!😅)。

总结

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

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

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

编码愉快 ✌️