Coverage for nova/cmd/common.py: 59%
90 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-17 15:08 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-17 15:08 +0000
1# Copyright 2016 Cloudbase Solutions Srl
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
16"""
17 Common functions used by different CLI interfaces.
18"""
20import argparse
21import inspect
23from oslo_log import log as logging
25import nova.conf
26import nova.db.main.api
27from nova import exception
28from nova.i18n import _
30CONF = nova.conf.CONF
31LOG = logging.getLogger(__name__)
34def validate_args(fn, *args, **kwargs):
35 """Check that the supplied args are sufficient for calling a function.
37 >>> validate_args(lambda a: None)
38 Traceback (most recent call last):
39 ...
40 MissingArgs: Missing argument(s): a
41 >>> validate_args(lambda a, b, c, d: None, 0, c=1)
42 Traceback (most recent call last):
43 ...
44 MissingArgs: Missing argument(s): b, d
46 :param fn: the function to check
47 :param arg: the positional arguments supplied
48 :param kwargs: the keyword arguments supplied
49 """
50 argspec = inspect.getfullargspec(fn)
52 num_defaults = len(argspec.defaults or [])
53 required_args = argspec.args[:len(argspec.args) - num_defaults]
55 if fn.__self__ is not None:
56 required_args.pop(0)
58 missing = [arg for arg in required_args if arg not in kwargs]
59 missing = missing[len(args):]
60 return missing
63# Decorators for actions
64def args(*args, **kwargs):
65 """Decorator which adds the given args and kwargs to the args list of
66 the desired func's __dict__.
67 """
68 def _decorator(func):
69 func.__dict__.setdefault('args', []).insert(0, (args, kwargs))
70 return func
71 return _decorator
74def methods_of(obj):
75 """Get all callable methods of an object that don't start with underscore
77 returns a list of tuples of the form (method_name, method)
78 """
79 result = []
80 for i in dir(obj):
81 if callable(getattr(obj, i)) and not i.startswith('_'):
82 result.append((i, getattr(obj, i)))
83 return result
86def add_command_parsers(subparsers, categories):
87 """Adds command parsers to the given subparsers.
89 Adds version and bash-completion parsers.
90 Adds a parser with subparsers for each category in the categories dict
91 given.
92 """
93 parser = subparsers.add_parser('version')
95 parser = subparsers.add_parser('bash-completion')
96 parser.add_argument('query_category', nargs='?')
98 for category in categories:
99 command_object = categories[category]()
101 desc = getattr(command_object, 'description', None)
102 parser = subparsers.add_parser(category, description=desc)
103 parser.set_defaults(command_object=command_object)
105 category_subparsers = parser.add_subparsers(dest='action')
106 category_subparsers.required = True
108 for (action, action_fn) in methods_of(command_object):
109 parser = category_subparsers.add_parser(
110 action, description=getattr(action_fn, 'description', desc))
112 action_kwargs = []
113 for args, kwargs in getattr(action_fn, 'args', []):
114 # we must handle positional parameters (ARG) separately from
115 # positional parameters (--opt). Detect this by checking for
116 # the presence of leading '--'
117 if args[0] != args[0].lstrip('-'):
118 kwargs.setdefault('dest', args[0].lstrip('-'))
119 if kwargs['dest'].startswith('action_kwarg_'):
120 action_kwargs.append(
121 kwargs['dest'][len('action_kwarg_'):])
122 else:
123 action_kwargs.append(kwargs['dest'])
124 kwargs['dest'] = 'action_kwarg_' + kwargs['dest']
125 else:
126 action_kwargs.append(args[0])
127 args = ['action_kwarg_' + arg for arg in args]
129 parser.add_argument(*args, **kwargs)
131 parser.set_defaults(action_fn=action_fn)
132 parser.set_defaults(action_kwargs=action_kwargs)
134 parser.add_argument('action_args', nargs='*',
135 help=argparse.SUPPRESS)
138def print_bash_completion(categories):
139 if not CONF.category.query_category:
140 print(" ".join(categories.keys()))
141 elif CONF.category.query_category in categories:
142 fn = categories[CONF.category.query_category]
143 command_object = fn()
144 actions = methods_of(command_object)
145 print(" ".join([k for (k, v) in actions]))
148def get_action_fn():
149 fn = CONF.category.action_fn
150 fn_args = []
151 for arg in CONF.category.action_args:
152 if isinstance(arg, bytes): 152 ↛ 153line 152 didn't jump to line 153 because the condition on line 152 was never true
153 arg = arg.decode('utf-8')
154 fn_args.append(arg)
156 fn_kwargs = {}
157 for k in CONF.category.action_kwargs:
158 v = getattr(CONF.category, 'action_kwarg_' + k)
159 if v is None:
160 continue
161 if isinstance(v, bytes): 161 ↛ 162line 161 didn't jump to line 162 because the condition on line 161 was never true
162 v = v.decode('utf-8')
163 fn_kwargs[k] = v
165 # call the action with the remaining arguments
166 # check arguments
167 missing = validate_args(fn, *fn_args, **fn_kwargs)
168 if missing:
169 # NOTE(mikal): this isn't the most helpful error message ever. It is
170 # long, and tells you a lot of things you probably don't want to know
171 # if you just got a single arg wrong.
172 print(fn.__doc__)
173 CONF.print_help()
174 raise exception.Invalid(
175 _("Missing arguments: %s") % ", ".join(missing))
177 return fn, fn_args, fn_kwargs
180def action_description(text):
181 """Decorator for adding a description to command action.
183 To display help text on action call instead of common category help text
184 action function can be decorated.
186 command <category> <action> -h will show description and arguments.
188 """
189 def _decorator(func):
190 func.description = text
191 return func
192 return _decorator