pallets / click

Python composable command line interface toolkit
https://click.palletsprojects.com
BSD 3-Clause "New" or "Revised" License
15.62k stars 1.4k forks source link

Allow adding dynamic parameters to every Context #2784

Open ofek opened 6 days ago

ofek commented 6 days ago

This adds a new dynamic_params property to the Context object which the Command object will use in its get_params method.

fixes #2783

davidism commented 6 days ago

I'm currently rewriting the parser, so I won't be able to add this until I understand how it will interact with that, which will be some time.

davidism commented 6 days ago

I'm not sure I like this, since it moves parameter definitions out of the command itself. We already support dynamic commands, and that doesn't require storing them in the context.

ofek commented 6 days ago

The issue is dynamic parameters rather than commands themselves. Here is an example that I'm about to use at work using this PR implementation:

def resolve_environment(ctx: DynamicContext, param: click.Option, value: str) -> str:  # noqa: ARG001
    from msgspec_click import generate_options

    dev_env_class = get_dev_env(value)
    ctx.params['env_class'] = dev_env_class
    ctx.dynamic_params.extend(generate_options(dev_env_class.config_class()))
    return value

@click.command(short_help='Start a developer environment', context_settings={'ignore_unknown_options': True})
@click.option(
    '--type',
    '-t',
    'env_type',
    type=click.Choice(AVAILABLE_DEV_ENVS),
    default=DEFAULT_DEV_ENV,
    show_default=True,
    is_eager=True,
    callback=resolve_environment,
    help='The type of developer environment',
)
@click.pass_context
def cmd(ctx: click.Context, env_type: str, **kwargs: Any) -> None:
    import msgspec

    env_class: type[DeveloperEnvironmentInterface] = ctx.params['env_class']
    config = msgspec.convert(kwargs, env_class.config_class())
    env = env_class(
        name=env_type,
        storage_dirs=app.config.storage,
        config=config,
    )
ofek commented 6 days ago

I would be open to anything that allows me to make dynamic options per execution/context rather than per command!

edit: without the use of a synchronization primitive like threading.Lock 😉

ofek commented 6 days ago

it moves parameter definitions out of the command itself

For this point in particular, I was trying to copy the code style by adding a parameter which is saved as a property of the instance. I actually don't see a use case for passing in parameters and would much prefer only the property without modifying the signature of Context. Would you like me to do that?

davidism commented 6 days ago

The issue is dynamic parameters rather than commands themselves.

I know. With dynamic commands, you can override what commands are available within a group during execution, without special support in the the context and without modifying the global definition of the group. I want dynamic parameters to behave similarly.

ofek commented 6 days ago

I don't have enough knowledge of the code to understand the paradigm you desire. Could you perhaps show an example?