osc-lib 3.2.0__py3-none-any.whl → 4.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
osc_lib/shell.py CHANGED
@@ -20,12 +20,15 @@ import getpass
20
20
  import logging
21
21
  import sys
22
22
  import traceback
23
+ import typing as ty
23
24
 
25
+ from cliff import _argparse
24
26
  from cliff import app
25
27
  from cliff import command
26
28
  from cliff import commandmanager
27
29
  from cliff import complete
28
30
  from cliff import help
31
+ from cliff import interactive
29
32
  from oslo_utils import importutils
30
33
  from oslo_utils import strutils
31
34
 
@@ -45,7 +48,7 @@ DEFAULT_DOMAIN = 'default'
45
48
  DEFAULT_INTERFACE = 'public'
46
49
 
47
50
 
48
- def prompt_for_password(prompt=None):
51
+ def prompt_for_password(prompt: ty.Optional[str] = None) -> str:
49
52
  """Prompt user for a password
50
53
 
51
54
  Prompt for a password if stdin is a tty.
@@ -76,26 +79,30 @@ def prompt_for_password(prompt=None):
76
79
  class OpenStackShell(app.App):
77
80
  CONSOLE_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s'
78
81
 
82
+ client_manager: clientmanager.ClientManager
83
+
79
84
  log = logging.getLogger(__name__)
80
- timing_data = []
85
+ timing_data: ty.List[ty.Any] = []
81
86
 
82
87
  def __init__(
83
88
  self,
84
- description=None,
85
- version=None,
86
- command_manager=None,
87
- stdin=None,
88
- stdout=None,
89
- stderr=None,
90
- interactive_app_factory=None,
91
- deferred_help=False,
92
- ):
89
+ description: ty.Optional[str] = None,
90
+ version: ty.Optional[str] = None,
91
+ command_manager: ty.Optional[commandmanager.CommandManager] = None,
92
+ stdin: ty.Optional[ty.TextIO] = None,
93
+ stdout: ty.Optional[ty.TextIO] = None,
94
+ stderr: ty.Optional[ty.TextIO] = None,
95
+ interactive_app_factory: ty.Optional[
96
+ ty.Type['interactive.InteractiveApp']
97
+ ] = None,
98
+ deferred_help: bool = False,
99
+ ) -> None:
93
100
  # Patch command.Command to add a default auth_required = True
94
- command.Command.auth_required = True
101
+ setattr(command.Command, 'auth_required', True)
95
102
 
96
103
  # Some commands do not need authentication
97
- help.HelpCommand.auth_required = False
98
- complete.CompleteCommand.auth_required = False
104
+ setattr(help.HelpCommand, 'auth_required', False)
105
+ setattr(complete.CompleteCommand, 'auth_required', False)
99
106
 
100
107
  # Slight change to the meaning of --debug
101
108
  self.DEFAULT_DEBUG_VALUE = None
@@ -120,17 +127,16 @@ class OpenStackShell(app.App):
120
127
  # Set in subclasses
121
128
  self.api_version = None
122
129
 
123
- self.client_manager = None
124
- self.command_options = None
130
+ self.command_options: ty.List[str] = []
125
131
 
126
132
  self.do_profile = False
127
133
 
128
- def configure_logging(self):
134
+ def configure_logging(self) -> None:
129
135
  """Configure logging for the app."""
130
136
  self.log_configurator = logs.LogConfigurator(self.options)
131
137
  self.dump_stack_trace = self.log_configurator.dump_trace
132
138
 
133
- def run(self, argv):
139
+ def run(self, argv: ty.List[str]) -> int:
134
140
  ret_val = 1
135
141
  self.command_options = argv
136
142
  try:
@@ -149,12 +155,12 @@ class OpenStackShell(app.App):
149
155
  finally:
150
156
  self.log.debug("END return value: %s", ret_val)
151
157
 
152
- def init_profile(self):
158
+ def init_profile(self) -> None:
153
159
  self.do_profile = osprofiler_profiler and self.options.profile
154
160
  if self.do_profile:
155
161
  osprofiler_profiler.init(self.options.profile)
156
162
 
157
- def close_profile(self):
163
+ def close_profile(self) -> None:
158
164
  if self.do_profile:
159
165
  profiler = osprofiler_profiler.get()
160
166
  trace_id = profiler.get_base_id()
@@ -167,14 +173,14 @@ class OpenStackShell(app.App):
167
173
  # that (PROFILE = 60 for instance), but not sure we need it here.
168
174
  self.log.warning(f"Trace ID: {trace_id}")
169
175
  self.log.warning(
170
- "Short trace ID " f"for OpenTracing-based drivers: {short_id}"
176
+ f"Short trace ID for OpenTracing-based drivers: {short_id}"
171
177
  )
172
178
  self.log.warning(
173
179
  "Display trace data with command:\n"
174
180
  f"osprofiler trace show --html {trace_id} "
175
181
  )
176
182
 
177
- def run_subcommand(self, argv):
183
+ def run_subcommand(self, argv: ty.List[str]) -> int:
178
184
  self.init_profile()
179
185
  try:
180
186
  ret_value = super().run_subcommand(argv)
@@ -182,7 +188,7 @@ class OpenStackShell(app.App):
182
188
  self.close_profile()
183
189
  return ret_value
184
190
 
185
- def interact(self):
191
+ def interact(self) -> None:
186
192
  self.init_profile()
187
193
  try:
188
194
  ret_value = super().interact()
@@ -190,8 +196,17 @@ class OpenStackShell(app.App):
190
196
  self.close_profile()
191
197
  return ret_value
192
198
 
193
- def build_option_parser(self, description, version):
194
- parser = super().build_option_parser(description, version)
199
+ def build_option_parser(
200
+ self,
201
+ description: ty.Optional[str],
202
+ version: ty.Optional[str],
203
+ argparse_kwargs: ty.Optional[ty.Dict[str, ty.Any]] = None,
204
+ ) -> _argparse.ArgumentParser:
205
+ parser = super().build_option_parser(
206
+ description,
207
+ version,
208
+ argparse_kwargs,
209
+ )
195
210
 
196
211
  # service token auth argument
197
212
  parser.add_argument(
@@ -248,9 +263,7 @@ class OpenStackShell(app.App):
248
263
  metavar='<auth-domain>',
249
264
  dest='default_domain',
250
265
  default=utils.env('OS_DEFAULT_DOMAIN', default=DEFAULT_DOMAIN),
251
- help=_(
252
- 'Default domain ID, default=%s. ' '(Env: OS_DEFAULT_DOMAIN)'
253
- )
266
+ help=_('Default domain ID, default=%s. (Env: OS_DEFAULT_DOMAIN)')
254
267
  % DEFAULT_DOMAIN,
255
268
  )
256
269
  parser.add_argument(
@@ -347,7 +360,6 @@ class OpenStackShell(app.App):
347
360
  )
348
361
 
349
362
  return parser
350
- # return clientmanager.build_plugin_option_parser(parser)
351
363
 
352
364
  """
353
365
  Break up initialize_app() so that overriding it in a subclass does not
@@ -363,7 +375,7 @@ class OpenStackShell(app.App):
363
375
 
364
376
  """
365
377
 
366
- def _final_defaults(self):
378
+ def _final_defaults(self) -> None:
367
379
  # Set the default plugin to None
368
380
  # NOTE(dtroyer): This is here to set up for setting it to a default
369
381
  # in the calling CLI
@@ -390,21 +402,21 @@ class OpenStackShell(app.App):
390
402
  # Save default domain
391
403
  self.default_domain = self.options.default_domain
392
404
 
393
- def _load_plugins(self):
405
+ def _load_plugins(self) -> None:
394
406
  """Load plugins via stevedore
395
407
 
396
408
  osc-lib has no opinion on what plugins should be loaded
397
409
  """
398
410
  pass
399
411
 
400
- def _load_commands(self):
412
+ def _load_commands(self) -> None:
401
413
  """Load commands via cliff/stevedore
402
414
 
403
415
  osc-lib has no opinion on what commands should be loaded
404
416
  """
405
417
  pass
406
418
 
407
- def initialize_app(self, argv):
419
+ def initialize_app(self, argv: ty.List[str]) -> None:
408
420
  """Global app init bits:
409
421
 
410
422
  * set up API versions
@@ -416,7 +428,9 @@ class OpenStackShell(app.App):
416
428
  super().initialize_app(argv)
417
429
  self.log.info(
418
430
  "START with options: %s",
419
- strutils.mask_password(" ".join(self.command_options)),
431
+ strutils.mask_password(" ".join(self.command_options))
432
+ if self.command_options
433
+ else "",
420
434
  )
421
435
  self.log.debug("options: %s", strutils.mask_password(self.options))
422
436
 
@@ -473,23 +487,23 @@ class OpenStackShell(app.App):
473
487
  pw_func=prompt_for_password,
474
488
  )
475
489
 
476
- def prepare_to_run_command(self, cmd):
490
+ def prepare_to_run_command(self, cmd: 'command.Command') -> None:
477
491
  """Set up auth and API versions"""
478
492
  self.log.debug(
479
493
  'command: %s -> %s.%s (auth=%s)',
480
494
  getattr(cmd, 'cmd_name', '<none>'),
481
495
  cmd.__class__.__module__,
482
496
  cmd.__class__.__name__,
483
- cmd.auth_required,
497
+ getattr(cmd, 'auth_required', None),
484
498
  )
485
499
 
486
500
  # NOTE(dtroyer): If auth is not required for a command, skip
487
501
  # get_one()'s validation to avoid loading plugins
488
- validate = cmd.auth_required
502
+ validate = getattr(cmd, 'auth_required', False)
489
503
 
490
504
  # NOTE(dtroyer): Save the auth required state of the _current_ command
491
505
  # in the ClientManager
492
- self.client_manager._auth_required = cmd.auth_required
506
+ self.client_manager._auth_required = validate
493
507
 
494
508
  # Validate auth options
495
509
  self.cloud = self.cloud_config.get_one(
@@ -503,26 +517,31 @@ class OpenStackShell(app.App):
503
517
  # Push the updated args into ClientManager
504
518
  self.client_manager._cli_options = self.cloud
505
519
 
506
- if cmd.auth_required:
520
+ if validate:
507
521
  self.client_manager.setup_auth()
508
522
  if hasattr(cmd, 'required_scope') and cmd.required_scope:
509
523
  # let the command decide whether we need a scoped token
510
524
  self.client_manager.validate_scope()
511
525
  # Trigger the Identity client to initialize
512
- self.client_manager.session.auth.auth_ref = (
526
+ self.client_manager.session.auth.auth_ref = ( # type: ignore
513
527
  self.client_manager.auth_ref
514
528
  )
515
529
  return
516
530
 
517
- def clean_up(self, cmd, result, err):
531
+ def clean_up(
532
+ self,
533
+ cmd: 'command.Command',
534
+ result: int,
535
+ err: ty.Optional[BaseException],
536
+ ) -> None:
518
537
  self.log.debug('clean_up %s: %s', cmd.__class__.__name__, err or '')
519
538
 
520
539
  # Close SDK connection if available to have proper cleanup there
521
- if hasattr(self.client_manager, "sdk_connection"):
540
+ if getattr(self.client_manager, "sdk_connection", None) is not None:
522
541
  self.client_manager.sdk_connection.close()
523
542
 
524
543
  # Close session if available
525
- if hasattr(self.client_manager.session, "session"):
544
+ if getattr(self.client_manager.session, "session", None) is not None:
526
545
  self.client_manager.session.session.close()
527
546
 
528
547
  # Process collected timing data
@@ -541,6 +560,7 @@ class OpenStackShell(app.App):
541
560
  # Check the formatter used in the actual command
542
561
  if (
543
562
  hasattr(cmd, 'formatter')
563
+ and hasattr(cmd, '_formatter_plugins')
544
564
  and cmd.formatter != cmd._formatter_plugins['table'].obj
545
565
  ):
546
566
  format = 'csv'
@@ -550,7 +570,7 @@ class OpenStackShell(app.App):
550
570
  tcmd.run(targs)
551
571
 
552
572
 
553
- def main(argv=None):
573
+ def main(argv: ty.Optional[ty.List[str]] = None) -> int:
554
574
  if argv is None:
555
575
  argv = sys.argv[1:]
556
576
  return OpenStackShell().run(argv)
@@ -288,19 +288,19 @@ class TestShellCli(utils.TestShell):
288
288
 
289
289
  # Default
290
290
  utils.fake_execute(_shell, "module list")
291
- self.assertEqual('', _shell.options.cert)
292
- self.assertEqual('', _shell.options.key)
291
+ self.assertIsNone(_shell.options.cert)
292
+ self.assertIsNone(_shell.options.key)
293
293
  self.assertIsNone(_shell.client_manager.cert)
294
294
 
295
295
  # --os-cert
296
296
  utils.fake_execute(_shell, "--os-cert mycert module list")
297
297
  self.assertEqual('mycert', _shell.options.cert)
298
- self.assertEqual('', _shell.options.key)
298
+ self.assertIsNone(_shell.options.key)
299
299
  self.assertEqual('mycert', _shell.client_manager.cert)
300
300
 
301
301
  # --os-key
302
302
  utils.fake_execute(_shell, "--os-key mickey module list")
303
- self.assertEqual('', _shell.options.cert)
303
+ self.assertIsNone(_shell.options.cert)
304
304
  self.assertEqual('mickey', _shell.options.key)
305
305
  self.assertIsNone(_shell.client_manager.cert)
306
306
 
@@ -14,7 +14,6 @@
14
14
  # under the License.
15
15
  #
16
16
 
17
- import contextlib
18
17
  import copy
19
18
  import json as jsonutils
20
19
  import os
@@ -111,24 +110,6 @@ class TestCase(testtools.TestCase):
111
110
  msg = f'method {m} should not have been called'
112
111
  self.fail(msg)
113
112
 
114
- @contextlib.contextmanager
115
- def subTest(self, *args, **kwargs):
116
- """This is a wrapper to unittest's subTest method.
117
-
118
- This wrapper suppresses 2 issues:
119
- * lack of support in older Python versions
120
- * bug in testtools that breaks support for all versions
121
- """
122
- try:
123
- with super().subTest(*args, **kwargs):
124
- yield
125
- except TypeError:
126
- raise
127
- except AttributeError:
128
- # TODO(elhararb): remove this except clause when subTest is
129
- # enabled in testtools
130
- yield
131
-
132
113
 
133
114
  class TestCommand(TestCase):
134
115
  """Test cliff command classes"""
@@ -13,6 +13,7 @@
13
13
  # under the License.
14
14
 
15
15
  import argparse
16
+ import functools
16
17
  import sys
17
18
  from unittest import mock
18
19
 
@@ -27,7 +28,9 @@ def help_enhancer(_h):
27
28
 
28
29
  class TestTags(test_utils.TestCase):
29
30
  def test_add_tag_filtering_option_to_parser(self):
30
- parser = argparse.ArgumentParser()
31
+ parser = argparse.ArgumentParser(
32
+ formatter_class=functools.partial(argparse.HelpFormatter, width=78)
33
+ )
31
34
  tags.add_tag_filtering_option_to_parser(parser, 'test')
32
35
 
33
36
  parsed_args = parser.parse_args(
@@ -60,7 +63,9 @@ class TestTags(test_utils.TestCase):
60
63
  self.assertCountEqual(expected, actual)
61
64
 
62
65
  def test_get_tag_filtering_args(self):
63
- parser = argparse.ArgumentParser()
66
+ parser = argparse.ArgumentParser(
67
+ formatter_class=functools.partial(argparse.HelpFormatter, width=78)
68
+ )
64
69
  tags.add_tag_filtering_option_to_parser(parser, 'test')
65
70
 
66
71
  parsed_args = parser.parse_args(
@@ -86,7 +91,9 @@ class TestTags(test_utils.TestCase):
86
91
  self.assertEqual(expected, args)
87
92
 
88
93
  def test_add_tag_option_to_parser_for_create(self):
89
- parser = argparse.ArgumentParser()
94
+ parser = argparse.ArgumentParser(
95
+ formatter_class=functools.partial(argparse.HelpFormatter, width=78)
96
+ )
90
97
  tags.add_tag_option_to_parser_for_create(parser, 'test')
91
98
 
92
99
  # Test that --tag and --no-tag are mutually exclusive
@@ -105,7 +112,9 @@ class TestTags(test_utils.TestCase):
105
112
  self.assertCountEqual(expected, actual)
106
113
 
107
114
  def test_add_tag_option_to_parser_for_set(self):
108
- parser = argparse.ArgumentParser()
115
+ parser = argparse.ArgumentParser(
116
+ formatter_class=functools.partial(argparse.HelpFormatter, width=78)
117
+ )
109
118
  tags.add_tag_option_to_parser_for_set(parser, 'test')
110
119
 
111
120
  parsed_args = parser.parse_args(['--tag', 'tag1'])
@@ -119,7 +128,9 @@ class TestTags(test_utils.TestCase):
119
128
  self.assertCountEqual(expected, actual)
120
129
 
121
130
  def test_add_tag_option_to_parser_for_unset(self):
122
- parser = argparse.ArgumentParser()
131
+ parser = argparse.ArgumentParser(
132
+ formatter_class=functools.partial(argparse.HelpFormatter, width=78)
133
+ )
123
134
  tags.add_tag_option_to_parser_for_unset(parser, 'test')
124
135
 
125
136
  # Test that --tag and --all-tag are mutually exclusive
@@ -209,11 +220,15 @@ class TestTagHelps(test_utils.TestCase):
209
220
  options_name = 'options'
210
221
  else:
211
222
  options_name = 'optional arguments'
212
- parser = argparse.ArgumentParser()
223
+ parser = argparse.ArgumentParser(
224
+ formatter_class=functools.partial(argparse.HelpFormatter, width=78)
225
+ )
213
226
  meth(parser, 'test')
214
227
  self.assertEqual(exp_normal % options_name, parser.format_help())
215
228
 
216
- parser = argparse.ArgumentParser()
229
+ parser = argparse.ArgumentParser(
230
+ formatter_class=functools.partial(argparse.HelpFormatter, width=78)
231
+ )
217
232
  meth(parser, 'test', enhance_help=help_enhancer)
218
233
  self.assertEqual(exp_enhanced % options_name, parser.format_help())
219
234
 
@@ -460,11 +460,6 @@ class TestUtils(test_utils.TestCase):
460
460
  self.assertEqual('fake-id', res_id)
461
461
  return res_attr
462
462
 
463
- def test_get_item_properties_with_format_func(self):
464
- formatters = {'attr': utils.format_list}
465
- res_attr = self._test_get_item_properties_with_formatter(formatters)
466
- self.assertEqual(utils.format_list(['a', 'b']), res_attr)
467
-
468
463
  def test_get_item_properties_with_formattable_column(self):
469
464
  formatters = {'attr': format_columns.ListColumn}
470
465
  res_attr = self._test_get_item_properties_with_formatter(formatters)
@@ -479,11 +474,6 @@ class TestUtils(test_utils.TestCase):
479
474
  self.assertEqual('fake-id', res_id)
480
475
  return res_attr
481
476
 
482
- def test_get_dict_properties_with_format_func(self):
483
- formatters = {'attr': utils.format_list}
484
- res_attr = self._test_get_dict_properties_with_formatter(formatters)
485
- self.assertEqual(utils.format_list(['a', 'b']), res_attr)
486
-
487
477
  def test_get_dict_properties_with_formattable_column(self):
488
478
  formatters = {'attr': format_columns.ListColumn}
489
479
  res_attr = self._test_get_dict_properties_with_formatter(formatters)
@@ -711,7 +701,7 @@ class TestFindResource(test_utils.TestCase):
711
701
  self.name,
712
702
  )
713
703
  self.assertEqual(
714
- "More than one resource exists " "with the name or ID 'legos'.",
704
+ "More than one resource exists with the name or ID 'legos'.",
715
705
  str(result),
716
706
  )
717
707
  self.manager.get.assert_called_with(self.name)