Skip to content
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

Command/Argument Preview Mechanism #152

Merged
merged 6 commits into from
May 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
.. :changelog:

Release History
===============

0.6.2
+++++
* Adds ability to declare that command groups, commands, and arguments are in a preview status and therefore might change or be removed. This is done by passing the kwarg `is_preview=True`.
* Adds a generic `TagDecorator` class to `knack.util` that allows you to create your own colorized tags like `[Preview]` and `[Deprecated]`.

0.6.1
+++++
* Always read from local for configured_default

0.6.0
+++++
* Support local context chained config file

0.5.4
+++++
* Allows the loading of text files using @filename syntax.
* Adds the argument kwarg configured_default to support setting argument defaults via the config file's [defaults] section or an environment variable.

0.5.3
+++++
* Removes an incorrect check when adding arguments.

0.5.2
+++++
* Updates usages of yaml.load to use yaml.safe_load.

0.5.1
+++++
* Fix issue with some scenarios (no args and --version)

0.5.0
+++++
* Adds support for positional arguments with the .positional helper method on ArgumentsContext.
* Removes the necessity for the type field in help.py. This information can be inferred from the class, so specifying it causes unnecessary crashes.
* Adds support for examining the result of a command after a call to invoke. The raw object, error (if any) an exit code are accessible.
* Adds support for accessing the command instance from inside custom commands by putting the special argument cmd in the signature.
* Fixes an issue with the default config directory. It use to be .cli and is now based on the CLI name.
* Fixes regression in knack 0.4.5 in behavior when cli_name --verbose/debug is used. Displays the welcome message as intended.
* Adds ability to specify line width for help text display.

0.4.5
+++++
* Preserves logging verbosity and output format on the namespace for use by validators.

0.4.4
+++++
* Adds ability to set config file name.
* Fixes bug with argument deprecations.

0.4.3
+++++
* Fixes issue where values were sometimes ignored when using deprecated options regardless of which option was given.

0.4.2
+++++
* Bug fix: disable number parse on table mode PR #88

0.4.1
+++++
* Fixes bug with deprecation mechanism.
* Fixes an issue where the command group table would only be filled by calls to create CommandGroup classes. This resulted in some gaps in the command group table.

0.4.0
+++++
* Add mechanism to deprecate commands, command groups, arguments and argument options.
* Improve help display support for Unicode.

0.3.3
+++++
* expose a callback to let client side perform extra logics (#80)
* output: don't skip false value on auto-tabulating (#83)

0.3.2
+++++
* ArgumentsContext.ignore() should use hidden options_list (#76)
* Consolidate exception handling (#66)

0.3.1
+++++
* Performance optimization - Delay import of platform and colorama (#47)
* CLIError: Inherit from Exception directly (#65)
* Explicitly state which packages to include (so exclude 'tests') (#68)

0.2.0
+++++
* Support command level and argument level validators.
* knack.commands.CLICommandsLoader now accepts a command_cls argument so you can provide your own CLICommand class.
* logging: make determine_verbose_level private method.
* Allow overriding of NAMED_ARGUMENTS
* Only pass valid argparse kwargs to argparse.ArgumentParser.add_argument and ignore the rest
* logging: make determine_verbose_level private method
* Remove cli_command, register_cli_argument, register_extra_cli_argument as ways to register commands and arguments.

0.1.1
+++++
* Add more types of command and argument loaders.

0.1.0
+++++
* Initial release
71 changes: 66 additions & 5 deletions knack/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from collections import defaultdict

from .deprecation import Deprecated
from .preview import PreviewItem
from .log import get_logger
from .util import CLIError

Expand Down Expand Up @@ -42,7 +43,7 @@ def update(self, other=None, **kwargs):

class CLICommandArgument(object):

NAMED_ARGUMENTS = ['options_list', 'validator', 'completer', 'arg_group', 'deprecate_info']
NAMED_ARGUMENTS = ['options_list', 'validator', 'completer', 'arg_group', 'deprecate_info', 'preview_info']

def __init__(self, dest=None, argtype=None, **kwargs):
"""An argument that has a specific destination parameter.
Expand Down Expand Up @@ -221,6 +222,55 @@ def __call__(self, parser, namespace, values, option_string=None):
action = _handle_option_deprecation(deprecated_opts)
return action

def _handle_previews(self, argument_dest, **kwargs):

if not kwargs.get('is_preview', False):
return kwargs

def _handle_argument_preview(preview_info):

parent_class = self._get_parent_class(**kwargs)

class PreviewArgumentAction(parent_class):

def __call__(self, parser, namespace, values, option_string=None):
if not hasattr(namespace, '_argument_previews'):
setattr(namespace, '_argument_previews', [preview_info])
else:
namespace._argument_previews.append(preview_info) # pylint: disable=protected-access
try:
super(PreviewArgumentAction, self).__call__(parser, namespace, values, option_string)
except NotImplementedError:
setattr(namespace, self.dest, values)

return PreviewArgumentAction

def _get_preview_arg_message(self):
return "{} '{}' is in preview. It may be changed/removed in a future release.".format(
self.object_type.capitalize(), self.target)

options_list = kwargs.get('options_list', None)
object_type = 'argument'

if options_list is None:
# convert argument dest
target = '--{}'.format(argument_dest.replace('_', '-'))
elif options_list:
target = sorted(options_list, key=len)[0]
else:
# positional argument
target = kwargs.get('metavar', '<{}>'.format(argument_dest.upper()))
object_type = 'positional argument'

preview_info = PreviewItem(
target=target,
object_type=object_type,
message_func=_get_preview_arg_message
)
kwargs['preview_info'] = preview_info
kwargs['action'] = _handle_argument_preview(preview_info)
return kwargs

# pylint: disable=inconsistent-return-statements
def deprecate(self, **kwargs):

Expand Down Expand Up @@ -252,7 +302,8 @@ def argument(self, argument_dest, arg_type=None, **kwargs):
:param arg_type: Predefined CLIArgumentType definition to register, as modified by any provided kwargs.
:type arg_type: knack.arguments.CLIArgumentType
:param kwargs: Possible values: `options_list`, `validator`, `completer`, `nargs`, `action`, `const`, `default`,
`type`, `choices`, `required`, `help`, `metavar`. See /docs/arguments.md.
`type`, `choices`, `required`, `help`, `metavar`, `is_preview`, `deprecate_info`.
See /docs/arguments.md.
"""
self._check_stale()
if not self._applicable():
Expand All @@ -261,6 +312,8 @@ def argument(self, argument_dest, arg_type=None, **kwargs):
deprecate_action = self._handle_deprecations(argument_dest, **kwargs)
if deprecate_action:
kwargs['action'] = deprecate_action

kwargs = self._handle_previews(argument_dest, **kwargs)
self.command_loader.argument_registry.register_cli_argument(self.command_scope,
argument_dest,
arg_type,
Expand All @@ -274,7 +327,8 @@ def positional(self, argument_dest, arg_type=None, **kwargs):
:param arg_type: Predefined CLIArgumentType definition to register, as modified by any provided kwargs.
:type arg_type: knack.arguments.CLIArgumentType
:param kwargs: Possible values: `validator`, `completer`, `nargs`, `action`, `const`, `default`,
`type`, `choices`, `required`, `help`, `metavar`. See /docs/arguments.md.
`type`, `choices`, `required`, `help`, `metavar`, `is_preview`, `deprecate_info`.
See /docs/arguments.md.
"""
self._check_stale()
if not self._applicable():
Expand All @@ -293,11 +347,14 @@ def positional(self, argument_dest, arg_type=None, **kwargs):
raise CLIError("command authoring error: commands may have, at most, one positional argument. '{}' already "
"has positional argument: {}.".format(self.command_scope, ' '.join(positional_args.keys())))

kwargs['options_list'] = []

deprecate_action = self._handle_deprecations(argument_dest, **kwargs)
if deprecate_action:
kwargs['action'] = deprecate_action

kwargs['options_list'] = []
kwargs = self._handle_previews(argument_dest, **kwargs)

self.command_loader.argument_registry.register_cli_argument(self.command_scope,
argument_dest,
arg_type,
Expand All @@ -323,7 +380,8 @@ def extra(self, argument_dest, **kwargs):
:param argument_dest: The destination argument to add this argument type to
:type argument_dest: str
:param kwargs: Possible values: `options_list`, `validator`, `completer`, `nargs`, `action`, `const`, `default`,
`type`, `choices`, `required`, `help`, `metavar`. See /docs/arguments.md.
`type`, `choices`, `required`, `help`, `metavar`, `is_preview`, `deprecate_info`.
See /docs/arguments.md.
"""
self._check_stale()
if not self._applicable():
Expand All @@ -337,6 +395,9 @@ def extra(self, argument_dest, **kwargs):
deprecate_action = self._handle_deprecations(argument_dest, **kwargs)
if deprecate_action:
kwargs['action'] = deprecate_action

kwargs = self._handle_previews(argument_dest, **kwargs)

self.command_loader.extra_argument_registry[self.command_scope][argument_dest] = CLICommandArgument(
argument_dest, **kwargs)

Expand Down
20 changes: 18 additions & 2 deletions knack/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import six

from .deprecation import Deprecated
from .preview import PreviewItem
from .prompting import prompt_y_n, NoTTYException
from .util import CLIError, CtxTypeError
from .arguments import ArgumentRegistry, CLICommandArgument
Expand All @@ -27,7 +28,8 @@ class CLICommand(object): # pylint:disable=too-many-instance-attributes
# pylint: disable=unused-argument
def __init__(self, cli_ctx, name, handler, description=None, table_transformer=None,
arguments_loader=None, description_loader=None,
formatter_class=None, deprecate_info=None, validator=None, confirmation=None, **kwargs):
formatter_class=None, deprecate_info=None, validator=None, confirmation=None, preview_info=None,
**kwargs):
""" The command object that goes into the command table.

:param cli_ctx: CLI Context
Expand All @@ -48,6 +50,8 @@ def __init__(self, cli_ctx, name, handler, description=None, table_transformer=N
:type formatter_class: class
:param deprecate_info: Deprecation message to display when this command is invoked
:type deprecate_info: str
:param preview_info: Indicates a command is in preview
:type preview_info: bool
:param validator: The command validator
:param confirmation: User confirmation required for command
:type confirmation: bool, str, callable
Expand All @@ -66,6 +70,7 @@ def __init__(self, cli_ctx, name, handler, description=None, table_transformer=N
self.table_transformer = table_transformer
self.formatter_class = formatter_class
self.deprecate_info = deprecate_info
self.preview_info = preview_info
self.confirmation = confirmation
self.validator = validator

Expand Down Expand Up @@ -295,6 +300,11 @@ def __init__(self, command_loader, group_name, operations_tmpl, **kwargs):
Deprecated.ensure_new_style_deprecation(self.command_loader.cli_ctx, self.group_kwargs, 'command group')
if kwargs['deprecate_info']:
kwargs['deprecate_info'].target = group_name
if kwargs.get('is_preview', False):
kwargs['preview_info'] = PreviewItem(
target=group_name,
object_type='command group'
)
command_loader._populate_command_group_table_with_subgroups(group_name) # pylint: disable=protected-access
self.command_loader.command_group_table[group_name] = self

Expand All @@ -313,7 +323,8 @@ def command(self, name, handler_name, **kwargs):
:type handler_name: str
:param kwargs: Kwargs to apply to the command.
Possible values: `client_factory`, `arguments_loader`, `description_loader`, `description`,
`formatter_class`, `table_transformer`, `deprecate_info`, `validator`, `confirmation`.
`formatter_class`, `table_transformer`, `deprecate_info`, `validator`, `confirmation`,
`is_preview`.
"""
import copy

Expand All @@ -322,6 +333,11 @@ def command(self, name, handler_name, **kwargs):
command_kwargs.update(kwargs)
# don't inherit deprecation info from command group
command_kwargs['deprecate_info'] = kwargs.get('deprecate_info', None)
if kwargs.get('is_preview', False):
command_kwargs['preview_info'] = PreviewItem(
self.command_loader.cli_ctx,
object_type='command'
)

self.command_loader._populate_command_group_table_with_subgroups(' '.join(command_name.split()[:-1])) # pylint: disable=protected-access
self.command_loader.command_table[command_name] = self.command_loader.create_command(
Expand Down
Loading