From 05a5d8fa878d8ae4d2ee8a1294feedc8af83fedb Mon Sep 17 00:00:00 2001 From: kai ru <69238381+kairu-ms@users.noreply.github.com> Date: Wed, 29 Jan 2025 08:13:42 +0800 Subject: [PATCH] {core} Fixed generic update issue. (#30703) * {core} --set: Minor fix * add test --- .../azure/cli/core/commands/arm.py | 12 ++++++------ .../azure/cli/core/tests/test_util.py | 18 ++++++++++++++++++ src/azure-cli-core/azure/cli/core/util.py | 14 ++++++++++++++ 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/commands/arm.py b/src/azure-cli-core/azure/cli/core/commands/arm.py index 90c1f89722..0769e4759d 100644 --- a/src/azure-cli-core/azure/cli/core/commands/arm.py +++ b/src/azure-cli-core/azure/cli/core/commands/arm.py @@ -15,7 +15,7 @@ from azure.cli.core.commands import LongRunningOperation from azure.cli.core.commands.client_factory import get_mgmt_service_client from azure.cli.core.commands.events import EVENT_INVOKER_PRE_LOAD_ARGUMENTS from azure.cli.core.commands.validators import IterateValue -from azure.cli.core.util import shell_safe_json_parse, get_command_type_kwarg +from azure.cli.core.util import shell_safe_json_parse, get_command_type_kwarg, getprop from azure.cli.core.profiles import ResourceType, get_sdk from knack.arguments import CLICommandArgument, ignore_type @@ -600,7 +600,7 @@ def remove_properties(instance, argument_values): def throw_and_show_options(instance, part, path): from msrest.serialization import Model options = instance.__dict__ if hasattr(instance, '__dict__') else instance - if isinstance(instance, Model) and isinstance(getattr(instance, 'additional_properties', None), dict): + if isinstance(instance, Model) and isinstance(getprop(instance, 'additional_properties', None), dict): options.update(options.pop('additional_properties')) parent = '.'.join(path[:-1]).replace('.[', '[') error_message = "Couldn't find '{}' in '{}'.".format(part, parent) @@ -673,7 +673,7 @@ def _update_instance(instance, part, path): # pylint: disable=too-many-return-s matches.append(x) elif not isinstance(x, dict): snake_key = make_snake_case(key) - if hasattr(x, snake_key) and getattr(x, snake_key, None) == value: + if hasattr(x, snake_key) and getprop(x, snake_key, None) == value: matches.append(x) if len(matches) == 1: @@ -681,7 +681,7 @@ def _update_instance(instance, part, path): # pylint: disable=too-many-return-s if len(matches) > 1: raise CLIError("non-unique key '{}' found multiple matches on {}. Key must be unique." .format(key, path[-2])) - if key in getattr(instance, 'additional_properties', {}): + if key in getprop(instance, 'additional_properties', {}): instance.enable_additional_properties_sending() return instance.additional_properties[key] raise CLIError("item with value '{}' doesn\'t exist for key '{}' on {}".format(value, key, path[-2])) @@ -697,8 +697,8 @@ def _update_instance(instance, part, path): # pylint: disable=too-many-return-s return instance[part] if hasattr(instance, make_snake_case(part)): - return getattr(instance, make_snake_case(part), None) - if part in getattr(instance, 'additional_properties', {}): + return getprop(instance, make_snake_case(part), None) + if part in getprop(instance, 'additional_properties', {}): instance.enable_additional_properties_sending() return instance.additional_properties[part] raise AttributeError() diff --git a/src/azure-cli-core/azure/cli/core/tests/test_util.py b/src/azure-cli-core/azure/cli/core/tests/test_util.py index f895b65bb9..54b7ce7e49 100644 --- a/src/azure-cli-core/azure/cli/core/tests/test_util.py +++ b/src/azure-cli-core/azure/cli/core/tests/test_util.py @@ -463,6 +463,24 @@ class TestBase64ToHex(unittest.TestCase): self.assertIsInstance(b64_to_hex(self.base64), str) +class TestGetProperty(unittest.TestCase): + + def test_getprop(self): + from azure.cli.core.util import getprop + with self.assertRaises(AttributeError): + getprop(self, '__class__') + with self.assertRaises(AttributeError): + getprop(self, '__init__') + with self.assertRaises(AttributeError): + getprop(self, 'assertRaises') + with self.assertRaises(AttributeError): + getprop(self, '_diffThreshold') + with self.assertRaises(AttributeError): + getprop(self, 'new_props') + self.assertEqual(getprop(self, 'maxDiff'), self.maxDiff) + self.assertEqual(getprop(self, 'new_props', "new_props"), "new_props") + + class TestHandleException(unittest.TestCase): @mock.patch('azure.cli.core.azclierror.logger.error', autospec=True) diff --git a/src/azure-cli-core/azure/cli/core/util.py b/src/azure-cli-core/azure/cli/core/util.py index 7d1e7b59a5..f8a531a9c1 100644 --- a/src/azure-cli-core/azure/cli/core/util.py +++ b/src/azure-cli-core/azure/cli/core/util.py @@ -1428,3 +1428,17 @@ def run_az_cmd(args, out_file=None): cli = get_default_cli() cli.invoke(args, out_file=out_file) return cli.result + + +def getprop(o, name, *default): + """ This function is used to get the property of the object. + It will raise an error if the property is a private property or a method. + """ + if name.startswith('_'): + # avoid to access the private properties or methods + raise AttributeError(name) + v = getattr(o, name, *default) + if callable(v): + # avoid to access the methods + raise AttributeError(name) + return v