Skip to content

Commit

Permalink
Remove arguments/sys.argv from pre/post_command actions (conda#12864)
Browse files Browse the repository at this point in the history
* Drop passing arguments to pre/post_commands

* Merge _run_command_hooks into manager

* Update tests

* Add missing type

* Update news

* Rename `apply_pre/post_commands` to `invoke_pre/post_commands`

Co-authored-by: Jannis Leidel <[email protected]>

* Remove `name` attribute from pre/post_commands

This is an unused attribute. The actual plugin name is defined in the entry_point so this attribute serves no purpose and simply confuses.

* Revert "Remove `name` attribute from pre/post_commands"

This reverts commit f82f171.

* Correct docs

---------

Co-authored-by: Jannis Leidel <[email protected]>
  • Loading branch information
kenodegard and jezdez authored Jul 14, 2023
1 parent de35993 commit 4894ef7
Show file tree
Hide file tree
Showing 11 changed files with 37 additions and 64 deletions.
23 changes: 4 additions & 19 deletions conda/cli/conda_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
from ..base.context import context
from ..common.constants import NULL
from ..deprecations import deprecated
from ..plugins.types import CommandHookTypes

log = getLogger(__name__)

Expand Down Expand Up @@ -117,9 +116,9 @@ def do_call(arguments: argparse.Namespace, parser: ArgumentParser):
"""
# First, check if this is a plugin subcommand; if this attribute is present then it is
if getattr(arguments, "plugin_subcommand", None):
_run_command_hooks("pre", arguments.plugin_subcommand.name, sys.argv[2:])
context.plugin_manager.invoke_pre_commands(arguments.plugin_subcommand.name)
result = arguments.plugin_subcommand.action(sys.argv[2:])
_run_command_hooks("post", arguments.plugin_subcommand.name, sys.argv[2:])
context.plugin_manager.invoke_post_commands(arguments.plugin_subcommand.name)

return result

Expand All @@ -130,27 +129,13 @@ def do_call(arguments: argparse.Namespace, parser: ArgumentParser):
module = import_module(relative_mod, __name__.rsplit(".", 1)[0])

command = relative_mod.replace(".main_", "")
_run_command_hooks("pre", command, arguments)
context.plugin_manager.invoke_pre_commands(command)
result = getattr(module, func_name)(arguments, parser)
_run_command_hooks("post", command, arguments)
context.plugin_manager.invoke_post_commands(command)

return result


def _run_command_hooks(hook_type: CommandHookTypes, command: str, arguments) -> None:
"""
Helper function used to gather applicable "pre" or "post" command hook functions
and then run them.
The values in *args are passed directly through to the "pre" or "post" command
hook function.
"""
actions = context.plugin_manager.yield_command_hook_actions(hook_type, command)

for action in actions:
action(command, arguments)


def find_builtin_commands(parser):
# ArgumentParser doesn't have an API for getting back what subparsers
# exist, so we need to use internal properties to do so.
Expand Down
4 changes: 2 additions & 2 deletions conda/plugins/hookspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ def conda_pre_commands(self) -> Iterable[CondaPreCommand]:
from conda import plugins
def example_pre_command(command, arguments):
def example_pre_command(command):
print("pre-command action")
Expand All @@ -158,7 +158,7 @@ def conda_post_commands(self) -> Iterable[CondaPostCommand]:
from conda import plugins
def example_post_command(command, arguments):
def example_post_command(command):
print("post-command action")
Expand Down
25 changes: 15 additions & 10 deletions conda/plugins/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from ..exceptions import CondaValueError, PluginError
from . import solvers, subcommands, virtual_packages
from .hookspec import CondaSpecs, spec_name
from .types import CommandHookTypes

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -132,7 +131,7 @@ def get_hook_results(self, name: str) -> list:
)
return plugins

def get_solver_backend(self, name: str = None) -> type[Solver]:
def get_solver_backend(self, name: str | None = None) -> type[Solver]:
"""
Get the solver backend with the given name (or fall back to the
name provided in the context).
Expand Down Expand Up @@ -167,19 +166,25 @@ def get_solver_backend(self, name: str = None) -> type[Solver]:

return backend

def yield_command_hook_actions(self, hook_type: CommandHookTypes, command: str):
def invoke_pre_commands(self, command: str) -> None:
"""
Yields either the ``CondaPreCommand.action`` or ``CondaPostCommand.action`` functions
registered by the ``conda_pre_commands`` or ``conda_post_commands`` hook.
Invokes ``CondaPreCommand.action`` functions registered with ``conda_pre_commands``.
:param hook_type: the type of command hook to retrieve
:param command: name of the command that is currently being invoked
"""
command_hooks = self.get_hook_results(f"{hook_type}_commands")
for hook in self.get_hook_results("pre_commands"):
if command in hook.run_for:
hook.action(command)

for command_hook in command_hooks:
if command in command_hook.run_for:
yield command_hook.action
def invoke_post_commands(self, command: str) -> None:
"""
Invokes ``CondaPostCommand.action`` functions registered with ``conda_post_commands``.
:param command: name of the command that is currently being invoked
"""
for hook in self.get_hook_results("post_commands"):
if command in hook.run_for:
hook.action(command)


@functools.lru_cache(maxsize=None) # FUTURE: Python 3.9+, replace w/ functools.cache
Expand Down
9 changes: 3 additions & 6 deletions conda/plugins/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,10 @@
"""
from __future__ import annotations

from typing import Callable, Literal, NamedTuple
from typing import Callable, NamedTuple

from ..core.solve import Solver

CommandHookTypes = Literal["pre", "post"]
"""The two different types of `conda_*_commands` hooks that are available"""


class CondaSubcommand(NamedTuple):
"""
Expand Down Expand Up @@ -81,7 +78,7 @@ class CondaPreCommand(NamedTuple):
"""

name: str
action: Callable
action: Callable[[str], None]
run_for: set[str]


Expand All @@ -98,5 +95,5 @@ class CondaPostCommand(NamedTuple):
"""

name: str
action: Callable
action: Callable[[str], None]
run_for: set[str]
6 changes: 3 additions & 3 deletions conda_env/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# when importing pip (and pip_util)
import conda.exports # noqa
from conda.base.context import context
from conda.cli.conda_argparse import ArgumentParser, _run_command_hooks
from conda.cli.conda_argparse import ArgumentParser
from conda.cli.main import init_loggers
from conda.exceptions import conda_exception_handler
from conda.gateways.logging import initialize_logging
Expand Down Expand Up @@ -49,10 +49,10 @@ def do_call(arguments, parser):
# Run the pre_command actions
command = relative_mod.replace(".main_", "")

_run_command_hooks("pre", f"env_{command}", arguments)
context.plugin_manager.invoke_pre_commands(f"env_{command}")
module = import_module(relative_mod, __name__.rsplit(".", 1)[0])
exit_code = getattr(module, func_name)(arguments, parser)
_run_command_hooks("post", f"env_{command}", arguments)
context.plugin_manager.invoke_post_commands(f"env_{command}")

return exit_code

Expand Down
5 changes: 2 additions & 3 deletions docs/source/dev-guide/plugins/post_commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ Post-commands
Conda commands can be extended with the ``conda_post_commands`` plugin hook.
By specifying the set of commands you would like to use in the ``run_for`` configuration
option, you can execute code via the ``action`` option after these commands are run.
The functions are provided ``command``, ``args`` and ``result`` as arguments which represent
the name of the command currently running, the command line arguments, and the result of the
running command, respectively. If the command fails for any reason, this plugin hook will not
The functions are provided a ``command`` argument representing the name
of the command currently running. If the command fails for any reason, this plugin hook will not
be run.

If you would like to target ``conda env`` commands, prefix the command name with ``env_``.
Expand Down
4 changes: 2 additions & 2 deletions docs/source/dev-guide/plugins/pre_commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ Pre-commands
Conda commands can be extended with the ``conda_pre_commands`` plugin hook.
By specifying the set of commands you would like to use in the ``run_for`` configuration
option, you can execute code via the ``action`` option before these commands are run.
The functions are provided ``command`` and ``args`` arguments which represent the name
of the command currently running and the command line arguments, respectively.
The functions are provided a ``command`` argument representing the name
of the command currently running.

If you would like to target ``conda env`` commands, prefix the command name with ``env_``.
For example, ``conda env list`` would be passed to ``run_for`` as ``env_list``.
Expand Down
2 changes: 1 addition & 1 deletion news/12712-pre-command-plugin-hook
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
### Enhancements

* Add a new "pre_commands" hook allowing plugins to run code before a `conda` subcommand. (#12712)
* Add a new "pre_commands" hook allowing plugins to run code before a `conda` subcommand. (#12712, #12864)

### Docs

Expand Down
2 changes: 1 addition & 1 deletion news/12758-post-command-plugin-hook
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
### Enhancements

* Add a new "post_commands" hook allowing plugins to run code before a `conda` subcommand. (#12758)
* Add a new "post_commands" hook allowing plugins to run code before a `conda` subcommand. (#12758, #12864)

### Docs

Expand Down
13 changes: 2 additions & 11 deletions tests/plugins/test_post_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,7 @@


class PostCommandPlugin:
def __init__(self):
self.invoked = False
self.args = None

@staticmethod
def post_command_action(command, args) -> int:
def post_command_action(self, command: str) -> int:
pass

@plugins.hookimpl
Expand All @@ -26,11 +21,7 @@ def conda_post_commands(self):

@pytest.fixture()
def post_command_plugin(mocker, plugin_manager):
mocker.patch.object(
PostCommandPlugin,
"post_command_action",
wraps=PostCommandPlugin.post_command_action,
)
mocker.patch.object(PostCommandPlugin, "post_command_action")

post_command_plugin = PostCommandPlugin()
plugin_manager.register(post_command_plugin)
Expand Down
8 changes: 2 additions & 6 deletions tests/plugins/test_pre_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@


class PreCommandPlugin:
def __init__(self):
self.invoked = False
self.args = None

def pre_command_action(self, command, args) -> int:
def pre_command_action(self, command: str) -> int:
pass

@plugins.hookimpl
Expand Down Expand Up @@ -57,7 +53,7 @@ def test_pre_command_action_raises_exception(pre_command_plugin, conda_cli):
that it bubbles up to the top and isn't caught anywhere. This will ensure that it
goes through our normal exception catching/reporting mechanism.
"""
exc_message = "Boom!"
exc_message = "💥"
pre_command_plugin.pre_command_action.side_effect = [Exception(exc_message)]

with pytest.raises(Exception, match=exc_message):
Expand Down

0 comments on commit 4894ef7

Please sign in to comment.