Skip to content

Commit

Permalink
Dependencies: update requirement to click~=8.0
Browse files Browse the repository at this point in the history
The entire logic around parameter parsing, including the consuming of
the value, optionally prompting and then processing the value has
changed. Especially the prompting logic has changed, which now makes the
custom logic in our `InteractiveOption` largely unnecessary. It had to
be refactored significantly and it now no longer changes the prompt
behavior but just overrides certain methods to include the concept of
non-interactivity which is not native to `click`.

In addition to this major change, there were also various smaller
changes. The following adaptations had to be made for compatibility:

 * Parameter validators now have to return the value, whereas before
   this was not required.
 * Custom parameter types need to start with checking for the value to
   already have the expected return type and then return it. This is
   necessary because the convert method can be called multiple times:
   https://click.palletsprojects.com/en/8.0.x/parameters/#implementing-custom-types
 * The `aiida.cmdline.params.options.contextualdefault.ContextualDefaultOption`
   has been removed as it was not used in `aiida-core` nor in any plugin
   that is hosted on Github.
 * `Parameter.get_default` now takes the `call` argument
 * Remove explicit parameter name from `version_option`, this is now
   by default set to just `--version`.
 * Add explicit `type` for `MultipleValueOption` options in `verdi run`.
   This is necessary because without it the entire string would be
   parsed as a single string and not a tuple of strings.
 * The `ConditionalOption` test `test_prompt_callback` had to be changed.
   With the old `click`, as soon as wrong input was provided at the
   prompt and the callback raised, the command would fail. With the new
   behavior of `click`, the user will be prompted until the callback
   validates, printing the error message each time. This required the
   test to be changed to actually pass a valid input at the end through
   the `user_input` or the test would hang as it was infinitely prompting.
 * The `Path` parameter removed the `path_type` attribute. It has been
   more or less been replaced by `name`.
 * The `click._compat.filename_ui` utility was removed.

Note that the lower requirement for `click` is set to `v8.0.3` since in
lower patch versions the behavior of prompts for boolean type parameters
is different.
  • Loading branch information
sphuber committed Oct 28, 2021
1 parent f5053d6 commit 623ef2b
Show file tree
Hide file tree
Showing 34 changed files with 1,021 additions and 1,315 deletions.
1 change: 0 additions & 1 deletion aiida/cmdline/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
'ProcessParamType',
'ProfileParamType',
'ShebangParamType',
'TestModuleParamType',
'UserParamType',
'WorkflowParamType',
'dbenv',
Expand Down
3 changes: 1 addition & 2 deletions aiida/cmdline/commands/cmd_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,7 @@ def code_duplicate(ctx, code, non_interactive, **kwargs):

code_builder = ctx.code_builder
for key, value in kwargs.items():
if value is not None:
setattr(code_builder, key, value)
setattr(code_builder, key, value)
new_code = code_builder.new()

try:
Expand Down
15 changes: 8 additions & 7 deletions aiida/cmdline/commands/cmd_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
###########################################################################
"""`verdi run` command."""
import contextlib
import functools
import os
import sys

Expand Down Expand Up @@ -38,14 +37,14 @@ def update_environment(argv):
sys.path = _path


def validate_entrypoint_string(ctx, param, value): # pylint: disable=unused-argument,invalid-name
def validate_entry_point_strings(ctx, param, value): # pylint: disable=unused-argument,invalid-name
"""Validate that `value` is a valid entrypoint string."""
from aiida.orm import autogroup

try:
autogroup.Autogroup.validate(value)
except Exception as exc:
raise click.BadParameter(f'{str(exc)} ({value})')
except (TypeError, ValueError) as exc:
raise click.BadParameter(f'{str(exc)}: `{value}`')

return value

Expand All @@ -65,18 +64,20 @@ def validate_entrypoint_string(ctx, param, value): # pylint: disable=unused-arg
@click.option(
'-e',
'--exclude',
type=str,
cls=MultipleValueOption,
default=None,
help='Exclude these classes from auto grouping (use full entrypoint strings).',
callback=functools.partial(validate_entrypoint_string)
callback=validate_entry_point_strings
)
@click.option(
'-i',
'--include',
type=str,
cls=MultipleValueOption,
default=None,
help='Include these classes from auto grouping (use full entrypoint strings or "all").',
callback=validate_entrypoint_string
help='Include these classes from auto grouping (use full entrypoint strings or "all").',
callback=validate_entry_point_strings
)
@decorators.with_dbenv()
def run(scriptname, varargs, auto_group, auto_group_label_prefix, exclude, include):
Expand Down
5 changes: 2 additions & 3 deletions aiida/cmdline/commands/cmd_verdi.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,10 @@ def group(self, *args, **kwargs):
return super().group(*args, **kwargs)


# Pass the version explicitly to ``version_option`` otherwise editable installs can show the wrong version number
@click.command(cls=VerdiCommandGroup, context_settings={'help_option_names': ['--help']})
@options.PROFILE(type=types.ProfileParamType(load_profile=True), expose_value=False)
@options.VERBOSITY()
# Note, __version__ should always be passed explicitly here,
# because click does not retrieve a dynamic version when installed in editable mode
@click.version_option(__version__, '--version', message='AiiDA version %(version)s')
@click.version_option(__version__, package_name='aiida_core', message='AiiDA version %(version)s')
def verdi():
"""The command line interface of AiiDA."""
1 change: 0 additions & 1 deletion aiida/cmdline/params/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
'ProcessParamType',
'ProfileParamType',
'ShebangParamType',
'TestModuleParamType',
'UserParamType',
'WorkflowParamType',
)
Expand Down
2 changes: 0 additions & 2 deletions aiida/cmdline/params/options/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
# pylint: disable=wildcard-import

from .config import *
from .contextualdefault import *
from .main import *
from .multivalue import *
from .overridable import *
Expand All @@ -41,7 +40,6 @@
'COMPUTERS',
'CONFIG_FILE',
'ConfigFileOption',
'ContextualDefaultOption',
'DATA',
'DATUM',
'DB_BACKEND',
Expand Down
44 changes: 17 additions & 27 deletions aiida/cmdline/params/options/conditional.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,56 +7,46 @@
# For further information on the license, see the LICENSE.txt file #
# For further information please visit http://www.aiida.net #
###########################################################################
"""
.. py:module::conditional
:synopsis: Tools for options which are required only if a a set of
conditions on the context are fulfilled
"""

"""Option whose requiredness is determined by a callback function."""
import click


class ConditionalOption(click.Option):
"""
This cli option takes an additional callable parameter and uses that
to determine wether a MissingParam should be raised if the option is
not given on the cli.
"""Option whose requiredness is determined by a callback function.
This option takes an additional callable parameter ``required_fn`` and uses that to determine whether a
``MissingParameter`` exception should be raised if no value is specified for the parameters.
The callable takes the context as an argument and can look up any
amount of other parameter values etc.
The callable should take the context as an argument which it can use to inspect the value of other parameters that
have been passed to the command invocation.
:param required_fn: callable(ctx) -> True | False, returns True
if the parameter is required to have a value.
This is typically used when the condition depends on other
parameters specified on the command line.
:param required_fn: callable(ctx) -> True | False, returns True if the parameter is required to have a value. This
is typically used when the condition depends on other parameters specified on the command line.
"""

def __init__(self, param_decls=None, required_fn=None, **kwargs):

# note default behaviour for required: False
self.required_fn = required_fn

# Required_fn overrides 'required', if defined
# If there is not callback to determine requiredness, assume the option is not required.
if required_fn is not None:
# There is a required_fn
self.required = False # So it does not show up as 'required'
self.required = False

super().__init__(param_decls=param_decls, **kwargs)

def full_process_value(self, ctx, value):
def process_value(self, ctx, value):
try:
value = super().full_process_value(ctx, value)
if self.required_fn and self.value_is_missing(value):
if self.is_required(ctx):
raise click.MissingParameter(ctx=ctx, param=self)
value = super().process_value(ctx, value)
except click.MissingParameter:
if self.is_required(ctx):
raise
else:
if self.required_fn and self.value_is_missing(value) and self.is_required(ctx):
raise click.MissingParameter(ctx=ctx, param=self)

return value

def is_required(self, ctx):
"""runs the given check on the context to determine requiredness"""

if self.required_fn:
return self.required_fn(ctx)

Expand Down
34 changes: 0 additions & 34 deletions aiida/cmdline/params/options/contextualdefault.py

This file was deleted.

Loading

0 comments on commit 623ef2b

Please sign in to comment.