Allow adding dynamic parameters to every Context
This adds a new dynamic_params property to the Context object which the Command object will use in its get_params method.
fixes #2783
- [X] Add tests that demonstrate the correct behavior of the change. Tests should fail without the change.
- [X] Add or update relevant docs, in the docs folder and in code.
- [X] Add an entry in CHANGES.rst summarizing the change and linking to the issue.
- [X] Add
.. versionchanged::entries in any relevant code docs.
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.
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.
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,
)
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 😉
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?
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.
I don't have enough knowledge of the code to understand the paradigm you desire. Could you perhaps show an example?
Edit: tests are now fixed and I added a new classmethod to Command that is required for this use case
The docs build error appears unrelated and I'm not sure how to fix that.
@ofek Thanks for submitting this. It will be a while before we are able to tend to this. We are currently focusing on getting issues cleaned up and patching bugs. So I am going to revert this to draft.
Should I rebase to resolve conflicts or is it not worth it currently?
I would say not worth the effort with where we are now. But if you would like to help with triaging issues, we would love the help.
@kdeldycke This has sat for a bit. If you have time, maybe you could take a look to see if there is an obvious way to support it in the way that @davidism suggests.