-
-
Notifications
You must be signed in to change notification settings - Fork 611
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
Validate parsed config against CLI options #1910
Conversation
@@ -573,21 +573,16 @@ def test_get_sys_path_for_python_executable(): | |||
@pytest.mark.parametrize( | |||
("pyproject_param", "new_default"), | |||
( | |||
# From sync | |||
("ask", True), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI: I have removed the options for pip-sync
(which led to test failures) because we only test the pip-compile CLI here. We could add a separate test for pip-sync with its own options, but it seems redundant to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we add the below condition? It would convert all negating boolean flags to their valid equivalents, e.g. no-annotate = true
-> annotate = false
, but error out in case these equivalents or the given values do not exist.
Of course the suggestions for non-valid flags would have to be adapted...
for key, value in config.items(): | ||
# Validate unknown keys | ||
if key not in cli_params: | ||
possibilities = difflib.get_close_matches(key, cli_params.keys()) | ||
raise click.NoSuchOption( | ||
option_name=key, | ||
message=f"No such config key {key!r}.", | ||
possibilities=possibilities, | ||
ctx=click_context, | ||
) | ||
|
||
# Validate invalid values | ||
param = cli_params[key] | ||
try: | ||
param.type.convert(value=value, param=param, ctx=click_context) | ||
except Exception as e: | ||
raise click.BadOptionUsage( | ||
option_name=key, | ||
message=f"Invalid value for config key {key!r}: {value!r}.", | ||
ctx=click_context, | ||
) from e |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for key, value in config.items(): | |
# Validate unknown keys | |
if key not in cli_params: | |
possibilities = difflib.get_close_matches(key, cli_params.keys()) | |
raise click.NoSuchOption( | |
option_name=key, | |
message=f"No such config key {key!r}.", | |
possibilities=possibilities, | |
ctx=click_context, | |
) | |
# Validate invalid values | |
param = cli_params[key] | |
try: | |
param.type.convert(value=value, param=param, ctx=click_context) | |
except Exception as e: | |
raise click.BadOptionUsage( | |
option_name=key, | |
message=f"Invalid value for config key {key!r}: {value!r}.", | |
ctx=click_context, | |
) from e | |
swap_keys = False | |
new_items = {} | |
old_items = {} | |
for key, value in config.items(): | |
# Validate unknown keys | |
if key not in cli_params: | |
# Replace boolean flag contexts like ``no_annotate`` with their equivalents | |
if key.startswith("no_"): | |
old_items[key] = value | |
key = key[3:] | |
value = not value | |
swap_keys = True | |
new_items[key] = value | |
else: | |
possibilities = difflib.get_close_matches(key, cli_params.keys()) | |
raise click.NoSuchOption( | |
option_name=key, | |
message=f"No such config key {key!r}.", | |
possibilities=possibilities, | |
ctx=click_context, | |
) | |
# Validate invalid values | |
param = cli_params[key] | |
try: | |
param.type.convert(value=value, param=param, ctx=click_context) | |
except Exception as e: | |
raise click.BadOptionUsage( | |
option_name=key, | |
message=f"Invalid value for config key {key!r}: {value!r}.", | |
ctx=click_context, | |
) from e | |
if swap_keys: | |
for key, value in old_items.items(): | |
del config[key] | |
for key, value in new_items.items(): | |
config[key] = value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense indeed. I would prefer to implement this in the parse_config_file()
function though. The _validate_config()
function does not intend a config
mutation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that function, you might be able to implement it like this:
swap_items = False
new_items = {}
old_items = {}
for key, value in piptools_config.items():
# Replace boolean flag contexts like ``no_annotate`` with their equivalents
if key.startswith("no_"):
swap_items = True
old_items[key] = value
if isinstance(value, bool):
value = not value
new_items[key[3:]] = value
if swap_items:
for key, value in old_items.items():
del piptools_config[key]
for key, value in new_items.items():
piptools_config[key] = value
return piptools_config
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you like to address this in a following up PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, thanks 👍🏻
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a reminder to myself: I think we can refactor parse_config_file
to parse options using the technique from the following snippet.
Snippet
(Pdb++) opts = {
opt: option
for option in ctx.command.params
for opt in itertools.chain(option.opts, option.secondary_opts)
}
(Pdb++) pp opts
{'--all-extras': <Option all_extras>,
'--allow-unsafe': <Option allow_unsafe>,
'--annotate': <Option annotate>,
'--no-annotate': <Option annotate >,
...
}
(Pdb++) opts['--pip-args'].name
'pip_args_str'
(Pdb++) opts['--annotate'].name
'annotate'
(Pdb++) opts['--no-annotate'].name
'annotate'
(Pdb++) opts['--extra'].name
'extras'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! I guess this would enable us to rationalise the function get_click_dest_for_option()
away. Would you like me to address that too, while I'm at it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, feel free to!
This looks good to me, thanks! Can you include the exception types to expect, |
@AndydeCleyre thanks! Addressed in 278d8df. |
Thanks @AndydeCleyre and @chrysle for the review! 🙏🏻 |
Refs: #1909.
Config key validation
Config value validation
Contributor checklist
Maintainer checklist
backwards incompatible
,feature
,enhancement
,deprecation
,bug
,dependency
,docs
orskip-changelog
as they determine changelog listing.