-
Notifications
You must be signed in to change notification settings - Fork 192
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
Improve deprecated messages from verdi setup
and verdi code setup
#6433
Improve deprecated messages from verdi setup
and verdi code setup
#6433
Conversation
c29671b
to
726a593
Compare
I removed the second solution in the second commit, because I think that makes reviewing more cumbersome. I think one can imagine how it looks like only keeping the deprecate in the help pages |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #6433 +/- ##
==========================================
+ Coverage 77.51% 77.87% +0.37%
==========================================
Files 560 562 +2
Lines 41444 41802 +358
==========================================
+ Hits 32120 32549 +429
+ Misses 9324 9253 -71
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
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.
Thanks @agoscinski . I am not sure I quite understand the use of the VerdiCommand
class. Why don't you simply use the deprecated
keyword of the click.Command
class? It would have made sense if you would have changed the type for verdi_deprecated
to str
but you declare it as a bool just as the one defined by click.
I like the approach though. And I am pretty sure that with some small changes you can even get the deprecation message to show before the prompts. All you need to do is:
- get rid of
VerdiCommand
- Just use
@click.command('name', deprecated='Use verdi profile setup core.psql_dos instead.') Now, I am passing a
streven though click types
deprecated` as just a bool. But since there are no strict type checks, this works - Replace your changes in
VerdiCommandGroup.get_command
withif cmd.deprecated: echo_deprecated(cmd.deprecated if isinstance(cmd.deprecated, str) else 'This command is deprecated.')
I think this solution has exactly the desired behavior. It prints the deprecation message before prompts and the commands are automatically prefixed with (Deprecated)
in the short help message in the help page.
@agoscinski do you have the time to finish this? Happy to take over from here if not |
I was using first the Reflecting on all solutions, accepting the fact that there are two deprecation message is worth the simplicity of the implementation (your suggestion) in my opinion. |
Yes, I think having two instead of 1 that comes too late is a much better situation. Another benefit is that the docstrings are automatically updated and we don't risk forgetting it. So please go far the suggested solution |
726a593
to
4948f5f
Compare
When implementing this solution I realized that This is because of this part in the code cmd_name, cmd, args = self.resolve_command(ctx, args)
assert cmd is not None
ctx.invoked_subcommand = cmd_name
super().invoke(ctx)
sub_ctx = cmd.make_context(cmd_name, args, parent=ctx)
with sub_ctx:
return _process_result(sub_ctx.command.invoke(sub_ctx)) https://github.com/pallets/click/blob/ac56c4551fcc5d4a60a66b252d6666bf58248e03/src/click/core.py#L1678-L1684 I did not find any attribute that gives me a hint in which context the |
There might be another solution. The formatting when you call def format_commands(self, ctx: Context, formatter: HelpFormatter) -> None:
"""Extra format methods for multi methods that adds all the commands
after the options.
"""
from gettext import gettext as _
commands = []
for subcommand in self.list_commands(ctx):
cmd = self.get_command(ctx, subcommand, print_deprecation=False)
# What is this, the tool lied about a command. Ignore it
if cmd is None:
continue
if cmd.hidden:
continue
commands.append((subcommand, cmd))
# allow for 3 times the default spacing
if len(commands):
limit = formatter.width - 6 - max(len(cmd[0]) for cmd in commands)
rows = []
for subcommand, cmd in commands:
help = cmd.get_short_help_str(limit)
rows.append((subcommand, help))
if rows:
with formatter.section(_("Commands")):
formatter.write_dl(rows) Unfortunately this is quite a bit of code. Only thing I had to change is add the import and then add You then update the signature of that method as: def get_command(self, ctx: click.Context, cmd_name: str, print_deprecation: bool = True) -> click.Command | None: And in the implementation you do: if cmd is not None:
if cmd.deprecated and not ctx.resilient_parsing and print_deprecation:
from aiida.cmdline.utils.echo import echo_deprecated
echo_deprecated(cmd.deprecated if isinstance(cmd.deprecated, str) else 'This command is deprecated.')
return self.add_verbosity_option(cmd) From some quick testing this seems to work as expected and desired. But I may have missed another edge case. Please give that a try though. Although not ideal that we have to completely copy the |
I feel like both solutions are equally not great in terms of adding extra logic that requires some deeper understanding how |
Fair enough. The one downside currently though is that if you are deprecating a command, you have to manually add the What is the reason that you chose to use the |
@agoscinski I had a look in the source code, and it is possible. |
It seemed to me most understandable point to insert this logic. The functions docstring """Given a context and a list of arguments this creates the parser
and parses the arguments, then modifies the context as necessary.
This is automatically invoked by :meth:`make_context`.
""" and the code logic shows this clearly in Command class VerdiCommand(click.Command):
"""Custom Command class to change logic how the deprecation message is handled."""
def parse_args(self, ctx: click.Context, args: t.List[str]) -> t.List[str]:
"""Prints the deprecation message before the arguments are parsed and any parameters requests are prompted in the parents class implementation
"""
if self.deprecated:
# We are abusing click.Command `deprecated` member variable by using a
# string instead of a bool to also use it as optional deprecated message
echo_deprecated(
self.deprecated
if isinstance(self.deprecated, str) # type: ignore[redundant-expr]
else 'This command is deprecated.'
)
return super().parse_args(ctx, args) |
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.
Thanks a lot @agoscinski . Think this is a great solution. Just have a suggested change for the docstring. If you are ok with it, we can merge.
91da784
to
e305232
Compare
Suggestion for squashed commit message (did not want to squash to make further review easier)
I thought about replacing everywhere usage of |
Fully agree, this can and should be done in a separate PR. We can leave the changes to the setup commands as you have already done them. Do you agree with my suggested change to the docstring? |
Yes, wanted to add them, but you have already. Should I rebase? |
Weird, the warning that fails the doc build is:
but we don't reference |
I think because it (the docstring) is inherited. Can I push a fix? |
The `click.Command` has a `deprecated` attribute, which is a bool, that when set to `True` results in a printed deprecation warning on usage, and adaptions of the help page to indicate the command as deprecated. The `click.option` prompts are however invoked before that warning is printed. This can lead to users filling out all prompts before they are finally greeted with the deprecation message. Therefore we customize the `click.Command` class introducing `aiida.cmdline.groups.VerdiCommand` to move the printing logic to before the first prompt is shown. The `VerdiCommandGroup` sets `command_class` to this new custom class such that all `verdi` commands automatically use it. To deprecate a command, the `deprecated` argument now just has to be set to the desired deprecation message.
3e3cf9d
to
048211c
Compare
Thanks a lot @agoscinski |
…ateam#6433) The `click.Command` has a `deprecated` attribute, which is a bool, that when set to `True` results in a printed deprecation warning on usage, and adaptions of the help page to indicate the command as deprecated. The `click.option` prompts are however invoked before that warning is printed. This can lead to users filling out all prompts before they are finally greeted with the deprecation message. Therefore we customize the `click.Command` class introducing `aiida.cmdline.groups.VerdiCommand` to move the printing logic to before the first prompt is shown. The `VerdiCommandGroup` sets `command_class` to this new custom class such that all `verdi` commands automatically use it. To deprecate a command, the `deprecated` argument now just has to be set to the desired deprecation message.
Goal
The two main problems I wanted to solve was to add a deprecation warning to the help page of
verdi setup
andverdi code setup
(easy) as well as printing the deprecation warning before the user has to go through all the prompts (hard). The solution became quite complicated, so I made a second solution that does not do this and just accepts that it is printed afterwards. There are two commits, one for each solution.Question
Right now my question is if the complexity introduced by making a new class
VerdiCommand
is worth to have the warning before the prompts. I am not sure. If we choose the second solution (warning comes after prompts), I would change some things (bring back thedeprecated_command
decorater as it gives us more flexibility).Technical aspects about the solution printing the warning before the prompt
Setting the
deprecated
flag fromclick.command
is not enough, since theclick.option
s are parsed (and the prompts appear) before the command is invoked where the logic for printing the deprecated warning lies. Therefore I usedVerdiCommandGroup.get_command
to print something before. This has the the effect that the deprecation warning is also printed when one requests the help page. One can argue if this is desired behavior.In principle I thought I could just use click.command's
deprecated
flag to consume it (set it to false after a print) inVerdiCommandGroup.get_command
, but then one gets problems with the validate_consistency test in the verdi-autodocs hook that does not know about this behavior and complains because the help page is not correct (deprecated flag changes also documentation). One could add also this logic to the validate_consistency.py but this adds another point where we need to know about the internal behavior of the cmdline that diverges from click, I was already not a big fan adding one.