From e7f084dfa92479d6e32a5abe51b873e0b1fba140 Mon Sep 17 00:00:00 2001 From: Stephen Watson Date: Tue, 18 Oct 2016 13:55:55 -0700 Subject: [PATCH] Implement Parameter Deprecation Implements parameter deprecation for magnum client via decorator in cliutils.py and additional logic in magnumclient/shell.py. Logic in shell creates mutual exclusive (mutex) groups in argparse to enable parameter checking for required and unrequired parameters specifically to check for old parameters that are being deprecated in favor of newer ones. Change-Id: Ic72c876e556070a066b1693a119a389301dfa259 Implements: blueprint rename-id-parameter-names --- magnumclient/common/cliutils.py | 61 +++++++++++++++++++++++++++++++-- magnumclient/shell.py | 30 ++++++++++++++++ 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/magnumclient/common/cliutils.py b/magnumclient/common/cliutils.py index bde8bffc..7c553234 100644 --- a/magnumclient/common/cliutils.py +++ b/magnumclient/common/cliutils.py @@ -35,6 +35,12 @@ from six import moves from magnumclient.i18n import _ +def deprecation_message(preamble, new_name): + msg = ('%s This parameter is deprecated and will be removed in a future ' + 'release. Use --%s instead.' % (preamble, new_name)) + return msg + + class MissingArgs(Exception): """Supplied arguments are not sufficient for calling a function.""" def __init__(self, missing): @@ -77,14 +83,14 @@ def validate_args(fn, *args, **kwargs): def deprecated(message): - '''Decorator for marking a call as deprecated by printing a given message. + """Decorator for marking a call as deprecated by printing a given message. Example: >>> @deprecated("Bay functions are deprecated and should be replaced by " ... "calls to cluster") ... def bay_create(args): ... pass - ''' + """ @decorator.decorator def wrapper(func, *args, **kwargs): print(message) @@ -92,6 +98,57 @@ def deprecated(message): return wrapper +def deprecation_map(dep_map): + """Decorator for applying a map of deprecating arguments to a function. + + The map connects deprecating arguments and their replacements. The + shell.py script uses this map to create mutually exclusive argument groups + in argparse and also prints a deprecation warning telling the user to + switch to the updated argument. + + NOTE: This decorator MUST be the outermost in the chain of argument + decorators to work correctly. + + Example usage: + >>> @deprecation_map({ "old-argument": "new-argument" }) + ... @args("old-argument", required=True) + ... @args("new-argument", required=True) + ... def do_command_line_stuff(): + ... pass + """ + def _decorator(func): + if not hasattr(func, 'arguments'): + return func + + func.deprecated_groups = [] + for old_param, new_param in dep_map.items(): + old_info, new_info = None, None + required = False + for (args, kwargs) in func.arguments: + if old_param in args: + old_info = (args, kwargs) + # Old arguments shouldn't be required if they were not + # previously, so prioritize old requirement + if 'required' in kwargs: + required = kwargs['required'] + # Set to false so argparse doesn't get angry + kwargs['required'] = False + elif new_param in args: + new_info = (args, kwargs) + kwargs['required'] = False + if old_info and new_info: + break + # Add a tuple of (old, new, required), which in turn is: + # ((old_args, old_kwargs), (new_args, new_kwargs), required) + func.deprecated_groups.append((old_info, new_info, required)) + # Remove arguments that would be duplicated by the groups we made + func.arguments.remove(old_info) + func.arguments.remove(new_info) + + return func + return _decorator + + def arg(*args, **kwargs): """Decorator for CLI args. diff --git a/magnumclient/shell.py b/magnumclient/shell.py index 5ddb992c..607d760d 100644 --- a/magnumclient/shell.py +++ b/magnumclient/shell.py @@ -459,6 +459,7 @@ class OpenStackMagnumShell(object): desc = callback.__doc__ or '' action_help = desc.strip() arguments = getattr(callback, 'arguments', []) + group_args = getattr(callback, 'deprecated_groups', []) subparser = ( subparsers.add_parser(command, @@ -471,6 +472,12 @@ class OpenStackMagnumShell(object): action='help', help=argparse.SUPPRESS,) self.subcommands[command] = subparser + + for (old_info, new_info, req) in group_args: + group = subparser.add_mutually_exclusive_group(required=req) + group.add_argument(*old_info[0], **old_info[1]) + group.add_argument(*new_info[0], **new_info[1]) + for (args, kwargs) in arguments: subparser.add_argument(*args, **kwargs) subparser.set_defaults(func=callback) @@ -630,8 +637,31 @@ class OpenStackMagnumShell(object): api_version=args.magnum_api_version, ) + self._check_deprecation(args.func, argv) args.func(self.cs, args) + def _check_deprecation(self, func, argv): + if not hasattr(func, 'deprecated_groups'): + return + + for (old_info, new_info, required) in func.deprecated_groups: + old_param = old_info[0][0] + new_param = new_info[0][0] + old_value, new_value = None, None + for i in range(len(argv)): + cur_arg = argv[i] + if cur_arg == old_param: + old_value = argv[i + 1] + elif cur_arg == new_param[0]: + new_value = argv[i + 1] + + if old_value and not new_value: + print( + 'WARNING: The %s parameter is deprecated and will be ' + 'removed in a future release. Use the %s parameter to ' + 'avoid seeing this message.' + % (old_param, new_param)) + def _dump_timings(self, timings): class Tyme(object): def __init__(self, url, seconds):