Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Call pre,post_command with more arguments (args, CLI invocation) #13924

Open
2 tasks done
jaimergp opened this issue May 16, 2024 · 4 comments
Open
2 tasks done

Call pre,post_command with more arguments (args, CLI invocation) #13924

jaimergp opened this issue May 16, 2024 · 4 comments
Labels
source::contributor created by a frequent contributor type::feature request for a new feature or capability

Comments

@jaimergp
Copy link
Contributor

Checklist

  • I added a descriptive title
  • I searched open requests and couldn't find a duplicate

What is the idea?

Plugins implementing the pre,post_command hooks should have access to the full invocation, not just the subcommand name. This allows for more flexible introspection of what to do under certain conditions.

Why is this needed?

Current implementation only passes the command name.

What should happen?

def do_call(args: argparse.Namespace, parser: ArgumentParser):
    """
    Serves as the primary entry point for commands referred to in this file and for
    all registered plugin subcommands.
    """
    # let's see if during the parsing phase it was discovered that the
    # called command was in fact a plugin subcommand
    if plugin_subcommand := getattr(args, "_plugin_subcommand", None):
        # pass on the rest of the plugin specific args or fall back to
        # the whole discovered arguments
        context.plugin_manager.invoke_pre_commands(plugin_subcommand.name)
        result = plugin_subcommand.action(getattr(args, "_args", args))
        context.plugin_manager.invoke_post_commands(plugin_subcommand.name)
        ...

Should become

def do_call(args: argparse.Namespace, parser: ArgumentParser):
    """
    Serves as the primary entry point for commands referred to in this file and for
    all registered plugin subcommands.
    """
    # let's see if during the parsing phase it was discovered that the
    # called command was in fact a plugin subcommand
    if plugin_subcommand := getattr(args, "_plugin_subcommand", None):
        # pass on the rest of the plugin specific args or fall back to
        # the whole discovered arguments
        context.plugin_manager.invoke_pre_commands(plugin_subcommand.name, args=args)
        result = plugin_subcommand.action(getattr(args, "_args", args))
        context.plugin_manager.invoke_post_commands(plugin_subcommand.name, args=args)
        ...

I guess we'll have to add a new plugin hook type PostCommandWithArgs or something similar to not break any API.

Additional Context

Finding this limitation in the development of conda-checkpoints, where I can't find the original command to annotate the checkpoint with. We can use sys.argv but that might be blank if called from the conda_cli() wrapper or other Python invocations.

@jaimergp jaimergp added the type::feature request for a new feature or capability label May 16, 2024
@travishathaway travishathaway added the source::contributor created by a frequent contributor label May 21, 2024
@travishathaway
Copy link
Contributor

travishathaway commented May 21, 2024

Is there a reason we can't just use?

from conda.base.context import context

context.raw_data["cmd_line"]

I know it's not the prettiest data to work with 😅. Here's what it looks like for conda info --base:

{
  'base': {
    'source': 'cmd_line', 'key': 'base', '_raw_value': True
  },
  'cmd': {
    'source': 'cmd_line', 'key': 'cmd', '_raw_value': 'info'
  },
  'envs': {
    'source': 'cmd_line', 'key': 'envs', '_raw_value': False
  },
  'func': {
    'source': 'cmd_line', 'key': 'func', '_raw_value': 'conda.cli.main_info.execute'
  },
  'license': {
    'source': 'cmd_line', 'key': 'license', '_raw_value': False
  },
  'system': {
    'source': 'cmd_line', 'key': 'system', '_raw_value': False
  },
  'unsafe_channels': {
    'source': 'cmd_line', 'key': 'unsafe_channels', '_raw_value': False
  }
}

That should give you everything you need though, right?

raw_data technically isn't a protected property of context either. Although, I'm sure it's probably not best practice to be reading directly from this object (e.g. if we ever change the underlying configuration system implementation). But, I think I would prefer that over making a new PostCommandWithArgs class.

Maybe instead, we should make something like "cli_args" available on the context object? Going forward this would appear the same regardless of the underlying configuration system implementation.

Just some food for though 🍎.

@jaimergp
Copy link
Contributor Author

Huh, I wasn't aware of that attribute. That could work. Although I do agree is probably a bad idea :P context.cli_args containing a dict dump of the argparse namespace doesn't sound like too bad of an idea, but we would need to see how this plays out with subcommand plugins modifying the parser as part of the initialization and so on... It should be ok for pre and post commands, though.

@jaimergp
Copy link
Contributor Author

we should make something like "cli_args" available on the context object?

Apparently we have it at context._argparse_args... 😬

@jaimergp
Copy link
Contributor Author

For those reading, I'm using context.raw_data["cmd_line"] as a way of querying the arguments. To note:

  • context.raw_data["cmd_line"] is a dictionary of str, RawArgParseArgument.
  • RawArgParseArgument is a context internal detail that happens to str as JSON. But it's actually an object.
  • bool(RawArgParseArgument(...)) is always true. To access the value via the public API, you must use RawArgParseArgument(...).value(None). There's a positional argument there that doesn't seem to be used in most cases, but you need to pass something.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
source::contributor created by a frequent contributor type::feature request for a new feature or capability
Projects
Status: 🆕 New
Development

No branches or pull requests

2 participants