osc-lib 3.2.0__py3-none-any.whl → 4.0.0__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/api/api.py +63 -27
- osc_lib/api/auth.py +36 -21
- osc_lib/api/utils.py +10 -5
- osc_lib/cli/client_config.py +51 -32
- osc_lib/cli/format_columns.py +19 -17
- osc_lib/cli/identity.py +14 -3
- osc_lib/cli/pagination.py +83 -0
- osc_lib/cli/parseractions.py +99 -20
- osc_lib/clientmanager.py +45 -24
- osc_lib/command/command.py +19 -8
- osc_lib/command/timing.py +11 -1
- osc_lib/exceptions.py +12 -2
- osc_lib/logs.py +18 -8
- osc_lib/py.typed +0 -0
- osc_lib/shell.py +64 -44
- osc_lib/tests/test_shell.py +4 -4
- osc_lib/tests/utils/__init__.py +0 -19
- osc_lib/tests/utils/test_tags.py +22 -7
- osc_lib/tests/utils/test_utils.py +1 -11
- osc_lib/utils/__init__.py +180 -108
- osc_lib/utils/columns.py +15 -4
- osc_lib/utils/tags.py +39 -21
- {osc_lib-3.2.0.dist-info → osc_lib-4.0.0.dist-info}/METADATA +11 -13
- osc_lib-4.0.0.dist-info/RECORD +53 -0
- {osc_lib-3.2.0.dist-info → osc_lib-4.0.0.dist-info}/WHEEL +1 -1
- osc_lib-4.0.0.dist-info/pbr.json +1 -0
- osc_lib-3.2.0.dist-info/RECORD +0 -51
- osc_lib-3.2.0.dist-info/pbr.json +0 -1
- {osc_lib-3.2.0.dist-info → osc_lib-4.0.0.dist-info}/AUTHORS +0 -0
- {osc_lib-3.2.0.dist-info → osc_lib-4.0.0.dist-info}/LICENSE +0 -0
- {osc_lib-3.2.0.dist-info → osc_lib-4.0.0.dist-info}/top_level.txt +0 -0
osc_lib/utils/__init__.py
CHANGED
@@ -15,25 +15,34 @@
|
|
15
15
|
|
16
16
|
"""Common client utilities"""
|
17
17
|
|
18
|
+
import argparse
|
19
|
+
import collections.abc
|
18
20
|
import copy
|
19
21
|
import functools
|
20
22
|
import getpass
|
21
23
|
import logging
|
22
24
|
import os
|
23
25
|
import time
|
26
|
+
import typing as ty
|
24
27
|
import warnings
|
25
28
|
|
26
29
|
from cliff import columns as cliff_columns
|
30
|
+
from openstack import resource
|
27
31
|
from oslo_utils import importutils
|
28
32
|
|
29
33
|
from osc_lib import exceptions
|
30
34
|
from osc_lib.i18n import _
|
31
35
|
|
32
|
-
|
33
36
|
LOG = logging.getLogger(__name__)
|
34
37
|
|
38
|
+
_T = ty.TypeVar('_T')
|
39
|
+
|
35
40
|
|
36
|
-
def backward_compat_col_lister(
|
41
|
+
def backward_compat_col_lister(
|
42
|
+
column_headers: list[str],
|
43
|
+
columns: list[str],
|
44
|
+
column_map: dict[str, str],
|
45
|
+
) -> list[str]:
|
37
46
|
"""Convert the column headers to keep column backward compatibility.
|
38
47
|
|
39
48
|
Replace the new column name of column headers by old name, so that
|
@@ -65,7 +74,11 @@ def backward_compat_col_lister(column_headers, columns, column_map):
|
|
65
74
|
return column_headers
|
66
75
|
|
67
76
|
|
68
|
-
def backward_compat_col_showone(
|
77
|
+
def backward_compat_col_showone(
|
78
|
+
show_object: collections.abc.MutableMapping[str, _T],
|
79
|
+
columns: list[str],
|
80
|
+
column_map: dict[str, str],
|
81
|
+
) -> collections.abc.MutableMapping[str, _T]:
|
69
82
|
"""Convert the output object to keep column backward compatibility.
|
70
83
|
|
71
84
|
Replace the new column name of output object by old name, so that
|
@@ -95,7 +108,7 @@ def backward_compat_col_showone(show_object, columns, column_map):
|
|
95
108
|
return show_object
|
96
109
|
|
97
110
|
|
98
|
-
def build_kwargs_dict(arg_name, value):
|
111
|
+
def build_kwargs_dict(arg_name: str, value: _T) -> dict[str, _T]:
|
99
112
|
"""Return a dictionary containing `arg_name` if `value` is set."""
|
100
113
|
kwargs = {}
|
101
114
|
if value:
|
@@ -103,7 +116,11 @@ def build_kwargs_dict(arg_name, value):
|
|
103
116
|
return kwargs
|
104
117
|
|
105
118
|
|
106
|
-
def calculate_header_and_attrs(
|
119
|
+
def calculate_header_and_attrs(
|
120
|
+
column_headers: collections.abc.Sequence[str],
|
121
|
+
attrs: collections.abc.Sequence[str],
|
122
|
+
parsed_args: argparse.Namespace,
|
123
|
+
) -> tuple[collections.abc.Sequence[str], collections.abc.Sequence[str]]:
|
107
124
|
"""Calculate headers and attribute names based on parsed_args.column.
|
108
125
|
|
109
126
|
When --column (-c) option is specified, this function calculates
|
@@ -138,7 +155,7 @@ def calculate_header_and_attrs(column_headers, attrs, parsed_args):
|
|
138
155
|
return column_headers, attrs
|
139
156
|
|
140
157
|
|
141
|
-
def env(*vars, **kwargs):
|
158
|
+
def env(*vars: str, **kwargs: ty.Any) -> ty.Optional[str]:
|
142
159
|
"""Search for the first defined of possibly many env vars
|
143
160
|
|
144
161
|
Returns the first environment variable defined in vars, or
|
@@ -148,10 +165,18 @@ def env(*vars, **kwargs):
|
|
148
165
|
value = os.environ.get(v, None)
|
149
166
|
if value:
|
150
167
|
return value
|
151
|
-
return kwargs.get('default', '')
|
152
168
|
|
169
|
+
if 'default' in kwargs and kwargs['default'] is not None:
|
170
|
+
return str(kwargs['default'])
|
171
|
+
|
172
|
+
return None
|
153
173
|
|
154
|
-
|
174
|
+
|
175
|
+
def find_min_match(
|
176
|
+
items: collections.abc.Sequence[_T],
|
177
|
+
sort_attr: str,
|
178
|
+
**kwargs: ty.Any,
|
179
|
+
) -> collections.abc.Sequence[_T]:
|
155
180
|
"""Find all resources meeting the given minimum constraints
|
156
181
|
|
157
182
|
:param items: A List of objects to consider
|
@@ -160,7 +185,7 @@ def find_min_match(items, sort_attr, **kwargs):
|
|
160
185
|
:rtype: A list of resources osrted by sort_attr that meet the minimums
|
161
186
|
"""
|
162
187
|
|
163
|
-
def minimum_pieces_of_flair(item):
|
188
|
+
def minimum_pieces_of_flair(item: _T) -> bool:
|
164
189
|
"""Find lowest value greater than the minumum"""
|
165
190
|
|
166
191
|
result = True
|
@@ -169,10 +194,17 @@ def find_min_match(items, sort_attr, **kwargs):
|
|
169
194
|
result = result and kwargs[k] <= get_field(item, k)
|
170
195
|
return result
|
171
196
|
|
172
|
-
return sort_items(filter(minimum_pieces_of_flair, items), sort_attr)
|
197
|
+
return sort_items(list(filter(minimum_pieces_of_flair, items)), sort_attr)
|
173
198
|
|
174
199
|
|
175
|
-
|
200
|
+
# TODO(stephenfin): We should return a proper type, but how to do so without
|
201
|
+
# using generics? We should also deprecate this but there are a lot of users
|
202
|
+
# still.
|
203
|
+
def find_resource(
|
204
|
+
manager: ty.Any,
|
205
|
+
name_or_id: str,
|
206
|
+
**kwargs: ty.Any,
|
207
|
+
) -> ty.Any:
|
176
208
|
"""Helper for the _find_* methods.
|
177
209
|
|
178
210
|
:param manager: A client manager class
|
@@ -200,7 +232,7 @@ def find_resource(manager, name_or_id, **kwargs):
|
|
200
232
|
# enough information, and domain information is necessary.
|
201
233
|
try:
|
202
234
|
return manager.get(name_or_id)
|
203
|
-
except Exception:
|
235
|
+
except Exception: # noqa: S110
|
204
236
|
pass
|
205
237
|
|
206
238
|
if kwargs:
|
@@ -208,7 +240,7 @@ def find_resource(manager, name_or_id, **kwargs):
|
|
208
240
|
# for example: /projects/demo&domain_id=30524568d64447fbb3fa8b7891c10dd
|
209
241
|
try:
|
210
242
|
return manager.get(name_or_id, **kwargs)
|
211
|
-
except Exception:
|
243
|
+
except Exception: # noqa: S110
|
212
244
|
pass
|
213
245
|
|
214
246
|
# Case 3: Try to get entity as integer id. Keystone does not have integer
|
@@ -242,7 +274,7 @@ def find_resource(manager, name_or_id, **kwargs):
|
|
242
274
|
kwargs[manager.resource_class.NAME_ATTR] = name_or_id
|
243
275
|
else:
|
244
276
|
kwargs['name'] = name_or_id
|
245
|
-
except Exception:
|
277
|
+
except Exception: # noqa: S110
|
246
278
|
pass
|
247
279
|
|
248
280
|
# finally try to find entity by name
|
@@ -279,26 +311,25 @@ def find_resource(manager, name_or_id, **kwargs):
|
|
279
311
|
# Case 5: For client with no find function, list all resources and hope
|
280
312
|
# to find a matching name or ID.
|
281
313
|
count = 0
|
282
|
-
for
|
283
|
-
if (
|
284
|
-
resource.get('id') == name_or_id
|
285
|
-
or resource.get('name') == name_or_id
|
286
|
-
):
|
314
|
+
for res in manager.list():
|
315
|
+
if res.get('id') == name_or_id or res.get('name') == name_or_id:
|
287
316
|
count += 1
|
288
|
-
|
317
|
+
_res = res
|
289
318
|
if count == 0:
|
290
319
|
# we found no match, report back this error:
|
291
320
|
msg = _("Could not find resource %s")
|
292
321
|
raise exceptions.CommandError(msg % name_or_id)
|
293
322
|
elif count == 1:
|
294
|
-
return
|
323
|
+
return _res
|
295
324
|
else:
|
296
325
|
# we found multiple matches, report back this error
|
297
326
|
msg = _("More than one resource exists with the name or ID '%s'.")
|
298
327
|
raise exceptions.CommandError(msg % name_or_id)
|
299
328
|
|
300
329
|
|
301
|
-
def format_dict(
|
330
|
+
def format_dict(
|
331
|
+
data: dict[str, ty.Any], prefix: ty.Optional[str] = None
|
332
|
+
) -> str:
|
302
333
|
"""Return a formatted string of key value pairs
|
303
334
|
|
304
335
|
:param data: a dict
|
@@ -326,11 +357,13 @@ def format_dict(data, prefix=None):
|
|
326
357
|
return output[:-2]
|
327
358
|
|
328
359
|
|
329
|
-
def format_dict_of_list(
|
360
|
+
def format_dict_of_list(
|
361
|
+
data: ty.Optional[dict[str, list[ty.Any]]], separator: str = '; '
|
362
|
+
) -> ty.Optional[str]:
|
330
363
|
"""Return a formatted string of key value pair
|
331
364
|
|
332
365
|
:param data: a dict, key is string, value is a list of string, for example:
|
333
|
-
{
|
366
|
+
{'public': ['2001:db8::8', '172.24.4.6']}
|
334
367
|
:param separator: the separator to use between key/value pair
|
335
368
|
(default: '; ')
|
336
369
|
:return: a string formatted to {'key1'=['value1', 'value2']} with separated
|
@@ -351,7 +384,9 @@ def format_dict_of_list(data, separator='; '):
|
|
351
384
|
return separator.join(output)
|
352
385
|
|
353
386
|
|
354
|
-
def format_list(
|
387
|
+
def format_list(
|
388
|
+
data: ty.Optional[list[ty.Any]], separator: str = ', '
|
389
|
+
) -> ty.Optional[str]:
|
355
390
|
"""Return a formatted strings
|
356
391
|
|
357
392
|
:param data: a list of strings
|
@@ -364,7 +399,9 @@ def format_list(data, separator=', '):
|
|
364
399
|
return separator.join(sorted(data))
|
365
400
|
|
366
401
|
|
367
|
-
def format_list_of_dicts(
|
402
|
+
def format_list_of_dicts(
|
403
|
+
data: ty.Optional[list[dict[str, ty.Any]]],
|
404
|
+
) -> ty.Optional[str]:
|
368
405
|
"""Return a formatted string of key value pairs for each dict
|
369
406
|
|
370
407
|
:param data: a list of dicts
|
@@ -376,10 +413,10 @@ def format_list_of_dicts(data):
|
|
376
413
|
return '\n'.join(format_dict(i) for i in data)
|
377
414
|
|
378
415
|
|
379
|
-
def format_size(size):
|
416
|
+
def format_size(size: ty.Union[int, float, None]) -> str:
|
380
417
|
"""Display size of a resource in a human readable format
|
381
418
|
|
382
|
-
:param
|
419
|
+
:param size:
|
383
420
|
The size of the resource in bytes.
|
384
421
|
|
385
422
|
:returns:
|
@@ -394,19 +431,22 @@ def format_size(size):
|
|
394
431
|
base = 1000.0
|
395
432
|
index = 0
|
396
433
|
|
397
|
-
if size is None
|
398
|
-
|
399
|
-
while size >= base:
|
434
|
+
size_ = float(size) if size is not None else 0.0
|
435
|
+
while size_ >= base:
|
400
436
|
index = index + 1
|
401
|
-
|
437
|
+
size_ = size_ / base
|
402
438
|
|
403
|
-
padded = f'{
|
439
|
+
padded = f'{size_:.1f}'
|
404
440
|
stripped = padded.rstrip('0').rstrip('.')
|
405
441
|
|
406
442
|
return f'{stripped}{suffix[index]}'
|
407
443
|
|
408
444
|
|
409
|
-
def get_client_class(
|
445
|
+
def get_client_class(
|
446
|
+
api_name: str,
|
447
|
+
version: ty.Union[str, int, float],
|
448
|
+
version_map: dict[str, type[_T]],
|
449
|
+
) -> ty.Any:
|
410
450
|
"""Returns the client class for the requested API version
|
411
451
|
|
412
452
|
:param api_name: the name of the API, e.g. 'compute', 'image', etc
|
@@ -414,6 +454,12 @@ def get_client_class(api_name, version, version_map):
|
|
414
454
|
:param version_map: a dict of client classes keyed by version
|
415
455
|
:rtype: a client class for the requested API version
|
416
456
|
"""
|
457
|
+
warnings.warn(
|
458
|
+
"This function is deprecated and is not necessary with openstacksdk."
|
459
|
+
"Consider vendoring this if necessary.",
|
460
|
+
category=DeprecationWarning,
|
461
|
+
)
|
462
|
+
|
417
463
|
try:
|
418
464
|
client_path = version_map[str(version)]
|
419
465
|
except (KeyError, ValueError):
|
@@ -436,7 +482,14 @@ def get_client_class(api_name, version, version_map):
|
|
436
482
|
return importutils.import_class(client_path)
|
437
483
|
|
438
484
|
|
439
|
-
def get_dict_properties(
|
485
|
+
def get_dict_properties(
|
486
|
+
item: dict[str, _T],
|
487
|
+
fields: collections.abc.Sequence[str],
|
488
|
+
mixed_case_fields: ty.Optional[collections.abc.Sequence[str]] = None,
|
489
|
+
formatters: ty.Optional[
|
490
|
+
dict[str, type[cliff_columns.FormattableColumn[ty.Any]]]
|
491
|
+
] = None,
|
492
|
+
) -> tuple[ty.Any, ...]:
|
440
493
|
"""Return a tuple containing the item properties.
|
441
494
|
|
442
495
|
:param item: a single dict resource
|
@@ -457,7 +510,7 @@ def get_dict_properties(item, fields, mixed_case_fields=None, formatters=None):
|
|
457
510
|
field_name = field.replace(' ', '_')
|
458
511
|
else:
|
459
512
|
field_name = field.lower().replace(' ', '_')
|
460
|
-
data = item[field_name] if field_name in item else ''
|
513
|
+
data: ty.Any = item[field_name] if field_name in item else ''
|
461
514
|
if field in formatters:
|
462
515
|
formatter = formatters[field]
|
463
516
|
# columns must be either a subclass of FormattableColumn
|
@@ -472,16 +525,7 @@ def get_dict_properties(item, fields, mixed_case_fields=None, formatters=None):
|
|
472
525
|
and issubclass(formatter.func, cliff_columns.FormattableColumn)
|
473
526
|
):
|
474
527
|
data = formatter(data)
|
475
|
-
# otherwise it's
|
476
|
-
elif callable(formatter):
|
477
|
-
warnings.warn(
|
478
|
-
'The usage of formatter functions is now discouraged. '
|
479
|
-
'Consider using cliff.columns.FormattableColumn instead. '
|
480
|
-
'See reviews linked with bug 1687955 for more detail.',
|
481
|
-
category=DeprecationWarning,
|
482
|
-
)
|
483
|
-
if data is not None:
|
484
|
-
data = formatter(data)
|
528
|
+
# otherwise it's invalid
|
485
529
|
else:
|
486
530
|
msg = "Invalid formatter provided."
|
487
531
|
raise exceptions.CommandError(msg)
|
@@ -490,31 +534,14 @@ def get_dict_properties(item, fields, mixed_case_fields=None, formatters=None):
|
|
490
534
|
return tuple(row)
|
491
535
|
|
492
536
|
|
493
|
-
def
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
for handler in root_log.handlers:
|
502
|
-
min_log_lvl = min(min_log_lvl, handler.level)
|
503
|
-
return min_log_lvl
|
504
|
-
|
505
|
-
|
506
|
-
def get_field(item, field):
|
507
|
-
try:
|
508
|
-
if isinstance(item, dict):
|
509
|
-
return item[field]
|
510
|
-
else:
|
511
|
-
return getattr(item, field)
|
512
|
-
except Exception:
|
513
|
-
msg = _("Resource doesn't have field %s")
|
514
|
-
raise exceptions.CommandError(msg % field)
|
515
|
-
|
516
|
-
|
517
|
-
def get_item_properties(item, fields, mixed_case_fields=None, formatters=None):
|
537
|
+
def get_item_properties(
|
538
|
+
item: dict[str, _T],
|
539
|
+
fields: collections.abc.Sequence[str],
|
540
|
+
mixed_case_fields: ty.Optional[collections.abc.Sequence[str]] = None,
|
541
|
+
formatters: ty.Optional[
|
542
|
+
dict[str, type[cliff_columns.FormattableColumn[ty.Any]]]
|
543
|
+
] = None,
|
544
|
+
) -> tuple[ty.Any, ...]:
|
518
545
|
"""Return a tuple containing the item properties.
|
519
546
|
|
520
547
|
:param item: a single item resource (e.g. Server, Project, etc)
|
@@ -538,19 +565,19 @@ def get_item_properties(item, fields, mixed_case_fields=None, formatters=None):
|
|
538
565
|
data = getattr(item, field_name, '')
|
539
566
|
if field in formatters:
|
540
567
|
formatter = formatters[field]
|
568
|
+
# columns must be either a subclass of FormattableColumn
|
541
569
|
if isinstance(formatter, type) and issubclass(
|
542
570
|
formatter, cliff_columns.FormattableColumn
|
543
571
|
):
|
544
572
|
data = formatter(data)
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
)
|
552
|
-
|
553
|
-
data = formatter(data)
|
573
|
+
# or a partial wrapping one (to allow us to pass extra parameters)
|
574
|
+
elif (
|
575
|
+
isinstance(formatter, functools.partial)
|
576
|
+
and isinstance(formatter.func, type)
|
577
|
+
and issubclass(formatter.func, cliff_columns.FormattableColumn)
|
578
|
+
):
|
579
|
+
data = formatter(data)
|
580
|
+
# otherwise it's invalid
|
554
581
|
else:
|
555
582
|
msg = "Invalid formatter provided."
|
556
583
|
raise exceptions.CommandError(msg)
|
@@ -559,7 +586,35 @@ def get_item_properties(item, fields, mixed_case_fields=None, formatters=None):
|
|
559
586
|
return tuple(row)
|
560
587
|
|
561
588
|
|
562
|
-
def
|
589
|
+
def get_effective_log_level() -> int:
|
590
|
+
"""Returns the lowest logging level considered by logging handlers
|
591
|
+
|
592
|
+
Retrieve and return the smallest log level set among the root
|
593
|
+
logger's handlers (in case of multiple handlers).
|
594
|
+
"""
|
595
|
+
root_log = logging.getLogger()
|
596
|
+
min_log_lvl = logging.CRITICAL
|
597
|
+
for handler in root_log.handlers:
|
598
|
+
min_log_lvl = min(min_log_lvl, handler.level)
|
599
|
+
return min_log_lvl
|
600
|
+
|
601
|
+
|
602
|
+
def get_field(item: _T, field: str) -> ty.Any:
|
603
|
+
try:
|
604
|
+
if isinstance(item, dict):
|
605
|
+
return item[field]
|
606
|
+
else:
|
607
|
+
return getattr(item, field)
|
608
|
+
except Exception:
|
609
|
+
msg = _("Resource doesn't have field %s")
|
610
|
+
raise exceptions.CommandError(msg % field)
|
611
|
+
|
612
|
+
|
613
|
+
def get_password(
|
614
|
+
stdin: ty.TextIO,
|
615
|
+
prompt: ty.Optional[str] = None,
|
616
|
+
confirm: bool = True,
|
617
|
+
) -> str:
|
563
618
|
message = prompt or "User Password:"
|
564
619
|
if hasattr(stdin, 'isatty') and stdin.isatty():
|
565
620
|
try:
|
@@ -579,19 +634,18 @@ def get_password(stdin, prompt=None, confirm=True):
|
|
579
634
|
raise exceptions.CommandError(msg)
|
580
635
|
|
581
636
|
|
582
|
-
def is_ascii(string):
|
637
|
+
def is_ascii(string: ty.Union[str, bytes]) -> bool:
|
583
638
|
try:
|
584
|
-
(
|
639
|
+
if isinstance(string, bytes):
|
585
640
|
string.decode('ascii')
|
586
|
-
|
587
|
-
|
588
|
-
)
|
641
|
+
else:
|
642
|
+
string.encode('ascii')
|
589
643
|
return True
|
590
644
|
except (UnicodeEncodeError, UnicodeDecodeError):
|
591
645
|
return False
|
592
646
|
|
593
647
|
|
594
|
-
def read_blob_file_contents(blob_file):
|
648
|
+
def read_blob_file_contents(blob_file: str) -> str:
|
595
649
|
try:
|
596
650
|
with open(blob_file) as file:
|
597
651
|
blob = file.read().strip()
|
@@ -601,7 +655,11 @@ def read_blob_file_contents(blob_file):
|
|
601
655
|
raise exceptions.CommandError(msg % blob_file)
|
602
656
|
|
603
657
|
|
604
|
-
def sort_items(
|
658
|
+
def sort_items(
|
659
|
+
items: collections.abc.Sequence[_T],
|
660
|
+
sort_str: str,
|
661
|
+
sort_type: ty.Optional[type[ty.Any]] = None,
|
662
|
+
) -> collections.abc.Sequence[_T]:
|
605
663
|
"""Sort items based on sort keys and sort directions given by sort_str.
|
606
664
|
|
607
665
|
:param items: a list or generator object of items
|
@@ -640,7 +698,7 @@ def sort_items(items, sort_str, sort_type=None):
|
|
640
698
|
if direction == 'desc':
|
641
699
|
reverse = True
|
642
700
|
|
643
|
-
def f(x):
|
701
|
+
def f(x: ty.Any) -> ty.Any:
|
644
702
|
# Attempts to convert items to same 'sort_type' if provided.
|
645
703
|
# This is due to Python 3 throwing TypeError if you attempt to
|
646
704
|
# compare different types
|
@@ -659,15 +717,15 @@ def sort_items(items, sort_str, sort_type=None):
|
|
659
717
|
|
660
718
|
|
661
719
|
def wait_for_delete(
|
662
|
-
manager,
|
663
|
-
res_id,
|
664
|
-
status_field='status',
|
665
|
-
error_status=['error'],
|
666
|
-
exception_name=['NotFound'],
|
667
|
-
sleep_time=5,
|
668
|
-
timeout=300,
|
669
|
-
callback=None,
|
670
|
-
):
|
720
|
+
manager: ty.Any,
|
721
|
+
res_id: str,
|
722
|
+
status_field: str = 'status',
|
723
|
+
error_status: collections.abc.Sequence[str] = ['error'],
|
724
|
+
exception_name: collections.abc.Sequence[str] = ['NotFound'],
|
725
|
+
sleep_time: int = 5,
|
726
|
+
timeout: int = 300,
|
727
|
+
callback: ty.Optional[collections.abc.Callable[[int], None]] = None,
|
728
|
+
) -> bool:
|
671
729
|
"""Wait for resource deletion
|
672
730
|
|
673
731
|
:param manager: the manager from which we can get the resource
|
@@ -685,6 +743,12 @@ def wait_for_delete(
|
|
685
743
|
:rtype: True on success, False if the resource has gone to error state or
|
686
744
|
the timeout has been reached
|
687
745
|
"""
|
746
|
+
warnings.warn(
|
747
|
+
"This function is deprecated as it does not support openstacksdk "
|
748
|
+
"clients. Consider vendoring this if necessary.",
|
749
|
+
category=DeprecationWarning,
|
750
|
+
)
|
751
|
+
|
688
752
|
total_time = 0
|
689
753
|
while total_time < timeout:
|
690
754
|
try:
|
@@ -712,14 +776,14 @@ def wait_for_delete(
|
|
712
776
|
|
713
777
|
|
714
778
|
def wait_for_status(
|
715
|
-
status_f,
|
716
|
-
res_id,
|
717
|
-
status_field='status',
|
718
|
-
success_status=['active'],
|
719
|
-
error_status=['error'],
|
720
|
-
sleep_time=5,
|
721
|
-
callback=None,
|
722
|
-
):
|
779
|
+
status_f: collections.abc.Callable[[str], object],
|
780
|
+
res_id: str,
|
781
|
+
status_field: str = 'status',
|
782
|
+
success_status: collections.abc.Sequence[str] = ['active'],
|
783
|
+
error_status: collections.abc.Sequence[str] = ['error'],
|
784
|
+
sleep_time: int = 5,
|
785
|
+
callback: ty.Optional[collections.abc.Callable[[int], None]] = None,
|
786
|
+
) -> bool:
|
723
787
|
"""Wait for status change on a resource during a long-running operation
|
724
788
|
|
725
789
|
:param status_f: a status function that takes a single id argument
|
@@ -731,6 +795,12 @@ def wait_for_status(
|
|
731
795
|
:param callback: called per sleep cycle, useful to display progress
|
732
796
|
:rtype: True on success
|
733
797
|
"""
|
798
|
+
warnings.warn(
|
799
|
+
"This function is deprecated as it does not support openstacksdk "
|
800
|
+
"clients. Consider vendoring this if necessary.",
|
801
|
+
category=DeprecationWarning,
|
802
|
+
)
|
803
|
+
|
734
804
|
while True:
|
735
805
|
res = status_f(res_id)
|
736
806
|
status = getattr(res, status_field, '').lower()
|
@@ -748,8 +818,10 @@ def wait_for_status(
|
|
748
818
|
|
749
819
|
|
750
820
|
def get_osc_show_columns_for_sdk_resource(
|
751
|
-
sdk_resource
|
752
|
-
|
821
|
+
sdk_resource: resource.Resource,
|
822
|
+
osc_column_map: dict[str, str],
|
823
|
+
invisible_columns: ty.Optional[collections.abc.Sequence[str]] = None,
|
824
|
+
) -> tuple[tuple[str, ...], tuple[str, ...]]:
|
753
825
|
"""Get and filter the display and attribute columns for an SDK resource.
|
754
826
|
|
755
827
|
Common utility function for preparing the output of an OSC show command.
|
osc_lib/utils/columns.py
CHANGED
@@ -11,13 +11,16 @@
|
|
11
11
|
# under the License.
|
12
12
|
|
13
13
|
import operator
|
14
|
+
import typing as ty
|
14
15
|
|
15
16
|
LIST_BOTH = 'both'
|
16
17
|
LIST_SHORT_ONLY = 'short_only'
|
17
18
|
LIST_LONG_ONLY = 'long_only'
|
18
19
|
|
19
20
|
|
20
|
-
def get_column_definitions(
|
21
|
+
def get_column_definitions(
|
22
|
+
attr_map: list[tuple[str, str, str]], long_listing: bool
|
23
|
+
) -> tuple[list[str], list[str]]:
|
21
24
|
"""Return table headers and column names for a listing table.
|
22
25
|
|
23
26
|
An attribute map (attr_map) is a list of table entry definitions
|
@@ -73,7 +76,10 @@ def get_column_definitions(attr_map, long_listing):
|
|
73
76
|
return headers, columns
|
74
77
|
|
75
78
|
|
76
|
-
def get_columns(
|
79
|
+
def get_columns(
|
80
|
+
item: dict[str, ty.Any],
|
81
|
+
attr_map: ty.Optional[list[tuple[str, str, str]]] = None,
|
82
|
+
) -> tuple[tuple[str, ...], tuple[str, ...]]:
|
77
83
|
"""Return pair of resource attributes and corresponding display names.
|
78
84
|
|
79
85
|
:param item: a dictionary which represents a resource.
|
@@ -82,7 +88,12 @@ def get_columns(item, attr_map=None):
|
|
82
88
|
|
83
89
|
.. code-block:: python
|
84
90
|
|
85
|
-
{
|
91
|
+
{
|
92
|
+
'id': 'myid',
|
93
|
+
'name': 'myname',
|
94
|
+
'foo': 'bar',
|
95
|
+
'tenant_id': 'mytenan',
|
96
|
+
}
|
86
97
|
|
87
98
|
:param attr_map: a list of mapping from attribute to display name.
|
88
99
|
The same format is used as for get_column_definitions attr_map.
|
@@ -106,7 +117,7 @@ def get_columns(item, attr_map=None):
|
|
106
117
|
in the alphabetical order.
|
107
118
|
Attributes not found in a given attr_map are kept as-is.
|
108
119
|
"""
|
109
|
-
attr_map = attr_map or
|
120
|
+
attr_map = attr_map or []
|
110
121
|
_attr_map_dict = dict((col, hdr) for col, hdr, listing_mode in attr_map)
|
111
122
|
|
112
123
|
columns = [
|