From 29dcfb40857dad87b49621bb56bc3ef0cf95a0d2 Mon Sep 17 00:00:00 2001 From: Yishi Wang Date: Thu, 14 Nov 2024 09:37:34 +0800 Subject: [PATCH 1/8] Add dict transformation for typespec generated SDKs --- src/azure-cli-core/azure/cli/core/commands/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/azure-cli-core/azure/cli/core/commands/__init__.py b/src/azure-cli-core/azure/cli/core/commands/__init__.py index f17e7862bba..0c8fb6c3a16 100644 --- a/src/azure-cli-core/azure/cli/core/commands/__init__.py +++ b/src/azure-cli-core/azure/cli/core/commands/__init__.py @@ -715,6 +715,10 @@ def _run_job(self, expanded_arg, cmd_copy): elif _is_paged(result): result = list(result) + # This is added for new models from typespec generated SDKs + # These models store data in `__dict__['_data']` instead of in `__dict__` + if result and hasattr(result, 'as_dict'): + result = result.as_dict() result = todict(result, AzCliCommandInvoker.remove_additional_prop_layer) event_data = {'result': result} From 0ceab943fb0274d9431a309634aa1bb917e3008e Mon Sep 17 00:00:00 2001 From: Yishi Wang Date: Thu, 14 Nov 2024 11:50:55 +0800 Subject: [PATCH 2/8] camel case --- .../azure/cli/core/commands/__init__.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/commands/__init__.py b/src/azure-cli-core/azure/cli/core/commands/__init__.py index 0c8fb6c3a16..4ebb54c40ba 100644 --- a/src/azure-cli-core/azure/cli/core/commands/__init__.py +++ b/src/azure-cli-core/azure/cli/core/commands/__init__.py @@ -38,7 +38,7 @@ from knack.preview import ImplicitPreviewItem, PreviewItem, resolve_preview_info from knack.experimental import ImplicitExperimentalItem, ExperimentalItem, resolve_experimental_info from knack.log import get_logger, CLILogging -from knack.util import CLIError, CommandResultItem, todict +from knack.util import CLIError, CommandResultItem, todict, to_camel_case from knack.events import EVENT_INVOKER_TRANSFORM_RESULT from knack.validators import DefaultStr @@ -117,6 +117,14 @@ def _pre_command_table_create(cli_ctx, args): return _expand_file_prefixed_files(args) +def _convert_camel_case(obj): + if isinstance(obj, dict): + result = {to_camel_case(k): _convert_camel_case(v) for (k, v) in obj.items()} + return result + if isinstance(obj, list): + return [_convert_camel_case(a) for a in obj] + return obj + # pylint: disable=too-many-instance-attributes class CacheObject: @@ -718,7 +726,7 @@ def _run_job(self, expanded_arg, cmd_copy): # This is added for new models from typespec generated SDKs # These models store data in `__dict__['_data']` instead of in `__dict__` if result and hasattr(result, 'as_dict'): - result = result.as_dict() + result = _convert_camel_case(result.as_dict()) result = todict(result, AzCliCommandInvoker.remove_additional_prop_layer) event_data = {'result': result} From 46875c6d2f69d60b6e2af22951e107dda29a2dca Mon Sep 17 00:00:00 2001 From: Yishi Wang Date: Fri, 15 Nov 2024 13:36:05 +0800 Subject: [PATCH 3/8] exclude autorest model --- src/azure-cli-core/azure/cli/core/commands/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-cli-core/azure/cli/core/commands/__init__.py b/src/azure-cli-core/azure/cli/core/commands/__init__.py index 4ebb54c40ba..ef2d3502a4c 100644 --- a/src/azure-cli-core/azure/cli/core/commands/__init__.py +++ b/src/azure-cli-core/azure/cli/core/commands/__init__.py @@ -725,7 +725,7 @@ def _run_job(self, expanded_arg, cmd_copy): # This is added for new models from typespec generated SDKs # These models store data in `__dict__['_data']` instead of in `__dict__` - if result and hasattr(result, 'as_dict'): + if result and hasattr(result, 'as_dict') and not hasattr(obj, '_attribute_map'): result = _convert_camel_case(result.as_dict()) result = todict(result, AzCliCommandInvoker.remove_additional_prop_layer) From d6d5be7fc8b9377b330d9482d751cbdb313835e4 Mon Sep 17 00:00:00 2001 From: Yishi Wang Date: Tue, 19 Nov 2024 11:17:42 +0800 Subject: [PATCH 4/8] fix typo --- src/azure-cli-core/azure/cli/core/commands/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-cli-core/azure/cli/core/commands/__init__.py b/src/azure-cli-core/azure/cli/core/commands/__init__.py index ef2d3502a4c..94de2dcc14e 100644 --- a/src/azure-cli-core/azure/cli/core/commands/__init__.py +++ b/src/azure-cli-core/azure/cli/core/commands/__init__.py @@ -725,7 +725,7 @@ def _run_job(self, expanded_arg, cmd_copy): # This is added for new models from typespec generated SDKs # These models store data in `__dict__['_data']` instead of in `__dict__` - if result and hasattr(result, 'as_dict') and not hasattr(obj, '_attribute_map'): + if result and hasattr(result, 'as_dict') and not hasattr(result, '_attribute_map'): result = _convert_camel_case(result.as_dict()) result = todict(result, AzCliCommandInvoker.remove_additional_prop_layer) From 344f39ef507e2616cf4eebc25229e08d683192c6 Mon Sep 17 00:00:00 2001 From: Yishi Wang Date: Tue, 19 Nov 2024 14:03:07 +0800 Subject: [PATCH 5/8] remove keyvault security domain output transformer --- src/azure-cli-core/azure/cli/core/commands/__init__.py | 1 + .../cli/command_modules/keyvault/_transformers.py | 10 ---------- .../azure/cli/command_modules/keyvault/commands.py | 10 ++++------ 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/commands/__init__.py b/src/azure-cli-core/azure/cli/core/commands/__init__.py index 94de2dcc14e..ef5977da700 100644 --- a/src/azure-cli-core/azure/cli/core/commands/__init__.py +++ b/src/azure-cli-core/azure/cli/core/commands/__init__.py @@ -125,6 +125,7 @@ def _convert_camel_case(obj): return [_convert_camel_case(a) for a in obj] return obj + # pylint: disable=too-many-instance-attributes class CacheObject: diff --git a/src/azure-cli/azure/cli/command_modules/keyvault/_transformers.py b/src/azure-cli/azure/cli/command_modules/keyvault/_transformers.py index ecc55ed67df..98647ab3d03 100644 --- a/src/azure-cli/azure/cli/command_modules/keyvault/_transformers.py +++ b/src/azure-cli/azure/cli/command_modules/keyvault/_transformers.py @@ -474,13 +474,3 @@ def transform_certificate_issuer_admin_list(result, **command_args): } for contact in admin_contacts] return ret return result - - -def transform_security_domain_output(result, **command_args): - if not result or isinstance(result, dict): - return result - ret = { - 'status': getattr(result, 'status', None), - 'statusDetails': getattr(result, 'status_details', None) - } - return ret diff --git a/src/azure-cli/azure/cli/command_modules/keyvault/commands.py b/src/azure-cli/azure/cli/command_modules/keyvault/commands.py index 90391133146..ce1747746cf 100644 --- a/src/azure-cli/azure/cli/command_modules/keyvault/commands.py +++ b/src/azure-cli/azure/cli/command_modules/keyvault/commands.py @@ -12,7 +12,7 @@ get_client, get_client_factory, Clients, is_azure_stack_profile) from azure.cli.command_modules.keyvault._transformers import ( - filter_out_managed_resources, transform_security_domain_output, + filter_out_managed_resources, multi_transformers, transform_key_decryption_output, keep_max_results, transform_key_list_output, transform_key_output, transform_key_encryption_output, transform_key_random_output, transform_secret_list, transform_deleted_secret_list, transform_secret_set, @@ -145,11 +145,9 @@ def load_command_table(self, _): with self.command_group('keyvault security-domain', data_security_domain_entity.command_type) as g: g.keyvault_custom('init-recovery', 'security_domain_init_recovery') g.keyvault_custom('restore-blob', 'security_domain_restore_blob') - g.keyvault_custom('upload', 'security_domain_upload', supports_no_wait=True, - transform=transform_security_domain_output) - g.keyvault_custom('download', 'security_domain_download', supports_no_wait=True, - transform=transform_security_domain_output) - g.keyvault_custom('wait', '_wait_security_domain_operation', transform=transform_security_domain_output) + g.keyvault_custom('upload', 'security_domain_upload', supports_no_wait=True) + g.keyvault_custom('download', 'security_domain_download', supports_no_wait=True) + g.keyvault_custom('wait', '_wait_security_domain_operation') with self.command_group('keyvault key', data_key_entity.command_type) as g: g.keyvault_custom('create', 'create_key', transform=transform_key_output, validator=validate_key_create) From 375b34d2c5016f3bff79f56134e070a12f9dfb05 Mon Sep 17 00:00:00 2001 From: Yishi Wang Date: Mon, 25 Nov 2024 10:49:36 +0800 Subject: [PATCH 6/8] rewrite knack.todict --- .../azure/cli/core/commands/__init__.py | 16 +------ src/azure-cli-core/azure/cli/core/util.py | 45 ++++++++++++++++++- 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/commands/__init__.py b/src/azure-cli-core/azure/cli/core/commands/__init__.py index ef5977da700..a929f9b5eeb 100644 --- a/src/azure-cli-core/azure/cli/core/commands/__init__.py +++ b/src/azure-cli-core/azure/cli/core/commands/__init__.py @@ -38,7 +38,7 @@ from knack.preview import ImplicitPreviewItem, PreviewItem, resolve_preview_info from knack.experimental import ImplicitExperimentalItem, ExperimentalItem, resolve_experimental_info from knack.log import get_logger, CLILogging -from knack.util import CLIError, CommandResultItem, todict, to_camel_case +from knack.util import CLIError, CommandResultItem from knack.events import EVENT_INVOKER_TRANSFORM_RESULT from knack.validators import DefaultStr @@ -117,15 +117,6 @@ def _pre_command_table_create(cli_ctx, args): return _expand_file_prefixed_files(args) -def _convert_camel_case(obj): - if isinstance(obj, dict): - result = {to_camel_case(k): _convert_camel_case(v) for (k, v) in obj.items()} - return result - if isinstance(obj, list): - return [_convert_camel_case(a) for a in obj] - return obj - - # pylint: disable=too-many-instance-attributes class CacheObject: @@ -724,10 +715,7 @@ def _run_job(self, expanded_arg, cmd_copy): elif _is_paged(result): result = list(result) - # This is added for new models from typespec generated SDKs - # These models store data in `__dict__['_data']` instead of in `__dict__` - if result and hasattr(result, 'as_dict') and not hasattr(result, '_attribute_map'): - result = _convert_camel_case(result.as_dict()) + from ..util import todict result = todict(result, AzCliCommandInvoker.remove_additional_prop_layer) event_data = {'result': result} diff --git a/src/azure-cli-core/azure/cli/core/util.py b/src/azure-cli-core/azure/cli/core/util.py index 8abc77884a7..9c84ee06c3d 100644 --- a/src/azure-cli-core/azure/cli/core/util.py +++ b/src/azure-cli-core/azure/cli/core/util.py @@ -18,7 +18,7 @@ from urllib.request import urlopen from knack.log import get_logger -from knack.util import CLIError, to_snake_case +from knack.util import CLIError, to_snake_case, to_camel_case logger = get_logger(__name__) @@ -624,6 +624,49 @@ def b64_to_hex(s): return hex_data +def recursively_to_camel_case(obj): + if isinstance(obj, dict): + result = {to_camel_case(k): recursively_to_camel_case(v) for (k, v) in obj.items()} + return result + if isinstance(obj, list): + return [recursively_to_camel_case(a) for a in obj] + return obj + + +def todict(obj, post_processor=None): + """ + Convert an object to a dictionary. Use 'post_processor(original_obj, dictionary)' to update the + dictionary in the process + """ + from datetime import date, time, datetime, timedelta + from enum import Enum + if isinstance(obj, dict): + result = {k: todict(v, post_processor) for (k, v) in obj.items()} + return post_processor(obj, result) if post_processor else result + if isinstance(obj, list): + return [todict(a, post_processor) for a in obj] + if isinstance(obj, Enum): + return obj.value + if isinstance(obj, (date, time, datetime)): + return obj.isoformat() + if isinstance(obj, timedelta): + return str(obj) + # This is the only difference with knack.util.todict because for typespec generated SDKs + # The base model stores data in obj.__dict__['_data'] instead of in obj.__dict__ + # We need to call obj.as_dict() to extract data for this kind of model + if hasattr(obj, 'as_dict') and not hasattr(obj, '_attribute_map'): + result = {to_camel_case(k): todict(v, post_processor) for k, v in obj.as_dict()} + return post_processor(obj, result) if post_processor else result + if hasattr(obj, '_asdict'): + return todict(obj._asdict(), post_processor) + if hasattr(obj, '__dict__'): + result = {to_camel_case(k): todict(v, post_processor) + for k, v in obj.__dict__.items() + if not callable(v) and not k.startswith('_')} + return post_processor(obj, result) if post_processor else result + return obj + + def random_string(length=16, force_lower=False, digits_only=False): from string import ascii_letters, digits, ascii_lowercase from random import choice From e66100385874c2148b3d0606920d5ed0a1fa10c6 Mon Sep 17 00:00:00 2001 From: Yishi Wang Date: Mon, 25 Nov 2024 10:54:12 +0800 Subject: [PATCH 7/8] remove unused func --- src/azure-cli-core/azure/cli/core/util.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/util.py b/src/azure-cli-core/azure/cli/core/util.py index 9c84ee06c3d..5f396226e66 100644 --- a/src/azure-cli-core/azure/cli/core/util.py +++ b/src/azure-cli-core/azure/cli/core/util.py @@ -624,15 +624,6 @@ def b64_to_hex(s): return hex_data -def recursively_to_camel_case(obj): - if isinstance(obj, dict): - result = {to_camel_case(k): recursively_to_camel_case(v) for (k, v) in obj.items()} - return result - if isinstance(obj, list): - return [recursively_to_camel_case(a) for a in obj] - return obj - - def todict(obj, post_processor=None): """ Convert an object to a dictionary. Use 'post_processor(original_obj, dictionary)' to update the From 185abc2821ff9981f9175a406f099dedcb078e8e Mon Sep 17 00:00:00 2001 From: Yishi Wang Date: Mon, 25 Nov 2024 11:20:58 +0800 Subject: [PATCH 8/8] fix typo --- src/azure-cli-core/azure/cli/core/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-cli-core/azure/cli/core/util.py b/src/azure-cli-core/azure/cli/core/util.py index 5f396226e66..886972fee1e 100644 --- a/src/azure-cli-core/azure/cli/core/util.py +++ b/src/azure-cli-core/azure/cli/core/util.py @@ -646,7 +646,7 @@ def todict(obj, post_processor=None): # The base model stores data in obj.__dict__['_data'] instead of in obj.__dict__ # We need to call obj.as_dict() to extract data for this kind of model if hasattr(obj, 'as_dict') and not hasattr(obj, '_attribute_map'): - result = {to_camel_case(k): todict(v, post_processor) for k, v in obj.as_dict()} + result = {to_camel_case(k): todict(v, post_processor) for k, v in obj.as_dict().items()} return post_processor(obj, result) if post_processor else result if hasattr(obj, '_asdict'): return todict(obj._asdict(), post_processor)