From 8c6f684bc2532f7fa9558c9c7f41c1a2f29b2c3d Mon Sep 17 00:00:00 2001 From: AllyW Date: Tue, 27 May 2025 21:31:24 +0800 Subject: [PATCH 1/6] add file as content --- .../azure/cli/core/aaz/__init__.py | 2 +- src/azure-cli-core/azure/cli/core/aaz/_arg.py | 14 +++++++++++- .../azure/cli/core/aaz/_arg_fmt.py | 22 +++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/aaz/__init__.py b/src/azure-cli-core/azure/cli/core/aaz/__init__.py index 6c53627cc6b..f0c4a1d1c47 100644 --- a/src/azure-cli-core/azure/cli/core/aaz/__init__.py +++ b/src/azure-cli-core/azure/cli/core/aaz/__init__.py @@ -13,7 +13,7 @@ AAZFreeFormDictArg, AAZFloatArg, AAZBaseArg, AAZBoolArg, AAZListArg, AAZResourceGroupNameArg, \ AAZResourceLocationArg, AAZResourceIdArg, AAZSubscriptionIdArg, AAZUuidArg, AAZDateArg, AAZTimeArg, \ AAZDateTimeArg, AAZDurationArg, AAZFileArg, AAZPasswordArg, AAZPaginationTokenArg, AAZPaginationLimitArg, \ - AAZAnyTypeArg + AAZAnyTypeArg, AAZFileBytesArg from ._arg_fmt import AAZStrArgFormat, AAZIntArgFormat, AAZFloatArgFormat, AAZBoolArgFormat, AAZObjectArgFormat, \ AAZDictArgFormat, AAZFreeFormDictArgFormat, AAZListArgFormat, AAZResourceLocationArgFormat, \ AAZResourceIdArgFormat, AAZSubscriptionIdArgFormat, AAZUuidFormat, AAZDateFormat, AAZTimeFormat, \ diff --git a/src/azure-cli-core/azure/cli/core/aaz/_arg.py b/src/azure-cli-core/azure/cli/core/aaz/_arg.py index 3c81dc9111b..95cac120edb 100644 --- a/src/azure-cli-core/azure/cli/core/aaz/_arg.py +++ b/src/azure-cli-core/azure/cli/core/aaz/_arg.py @@ -22,7 +22,7 @@ from ._arg_fmt import AAZObjectArgFormat, AAZListArgFormat, AAZDictArgFormat, AAZFreeFormDictArgFormat, \ AAZSubscriptionIdArgFormat, AAZResourceLocationArgFormat, AAZResourceIdArgFormat, AAZUuidFormat, AAZDateFormat, \ AAZTimeFormat, AAZDateTimeFormat, AAZDurationFormat, AAZFileArgTextFormat, AAZPaginationTokenArgFormat, \ - AAZIntArgFormat + AAZIntArgFormat, AAZFileBytesArgFormat from .exceptions import AAZUnregisteredArg from ._prompt import AAZPromptInput @@ -599,6 +599,18 @@ def __init__(self, fmt=None, **kwargs): super().__init__(fmt=fmt, **kwargs) +class AAZFileBytesArg(AAZStrArg): + """Argument that accepts a file path and returns both file content in bytes""" + + def __init__(self, fmt=None, **kwargs): + fmt = fmt or AAZFileBytesArgFormat() + super().__init__(fmt=fmt, **kwargs) + + @property + def _type_in_help(self): + return "File Bytes" + + # Generic Update arguments class AAZGenericUpdateForceStringArg(AAZBoolArg): diff --git a/src/azure-cli-core/azure/cli/core/aaz/_arg_fmt.py b/src/azure-cli-core/azure/cli/core/aaz/_arg_fmt.py index 42d26cf6545..77ba87fb45f 100644 --- a/src/azure-cli-core/azure/cli/core/aaz/_arg_fmt.py +++ b/src/azure-cli-core/azure/cli/core/aaz/_arg_fmt.py @@ -748,6 +748,28 @@ def read_file(self, file_path): return data +class AAZFileBytesArgFormat(AAZBaseArgFormat): + """Format for file info argument that returns both filename and content""" + + def __call__(self, ctx, value): + assert isinstance(value, AAZSimpleValue) + data = value._data + if data == AAZUndefined or data is None or value._is_patch: + return value + + assert isinstance(data, str) + + if not os.path.isfile(data): + raise AAZInvalidArgValueError(f"File '{data}' doesn't exist") + + try: + with open(data, 'rb') as f: + value._data = f.read() + return value + except Exception as e: + raise AAZInvalidArgValueError(f"Failed to read file '{data}': {str(e)}") + + class AAZPaginationTokenArgFormat(AAZBaseArgFormat): def __call__(self, ctx, value): def validate_json(s): From f03605f5fbf74c67c7f8d5751585d904f1e355aa Mon Sep 17 00:00:00 2001 From: AllyW Date: Thu, 29 May 2025 13:54:02 +0800 Subject: [PATCH 2/6] add type --- src/azure-cli-core/azure/cli/core/aaz/__init__.py | 2 +- src/azure-cli-core/azure/cli/core/aaz/_field_type.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/azure-cli-core/azure/cli/core/aaz/__init__.py b/src/azure-cli-core/azure/cli/core/aaz/__init__.py index f0c4a1d1c47..d340af29d64 100644 --- a/src/azure-cli-core/azure/cli/core/aaz/__init__.py +++ b/src/azure-cli-core/azure/cli/core/aaz/__init__.py @@ -23,7 +23,7 @@ from ._command import AAZCommand, AAZWaitCommand, AAZCommandGroup, \ register_callback, register_command, register_command_group, load_aaz_command_table, link_helper from ._field_type import AAZIntType, AAZFloatType, AAZStrType, AAZBoolType, AAZDictType, AAZFreeFormDictType, \ - AAZListType, AAZObjectType, AAZIdentityObjectType, AAZAnyType + AAZListType, AAZObjectType, AAZIdentityObjectType, AAZAnyType, AAZBytesType from ._operation import AAZHttpOperation, AAZJsonInstanceUpdateOperation, AAZGenericInstanceUpdateOperation, \ AAZJsonInstanceDeleteOperation, AAZJsonInstanceCreateOperation from ._prompt import AAZPromptInput, AAZPromptPasswordInput diff --git a/src/azure-cli-core/azure/cli/core/aaz/_field_type.py b/src/azure-cli-core/azure/cli/core/aaz/_field_type.py index 4638d108ecd..5cdc375ce2d 100644 --- a/src/azure-cli-core/azure/cli/core/aaz/_field_type.py +++ b/src/azure-cli-core/azure/cli/core/aaz/_field_type.py @@ -111,6 +111,10 @@ def process_data(self, data, **kwargs): return data +class AAZBytesType(AAZSimpleType): + DataType = bytes + + class AAZAnyType(AAZSimpleType): """Any type""" From 92c3b81e76791c015d0a2b3b30f89d260037c637 Mon Sep 17 00:00:00 2001 From: AllyW Date: Thu, 29 May 2025 16:46:21 +0800 Subject: [PATCH 3/6] fix style --- .../azure/cli/core/aaz/__init__.py | 4 +-- src/azure-cli-core/azure/cli/core/aaz/_arg.py | 25 +++++++++-------- .../azure/cli/core/aaz/_arg_action.py | 27 +++++++++++++++++++ .../azure/cli/core/aaz/_arg_fmt.py | 22 --------------- .../azure/cli/core/aaz/_field_type.py | 6 ++++- .../azure/cli/core/aaz/_field_value.py | 18 +++++++++++++ 6 files changed, 66 insertions(+), 36 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/aaz/__init__.py b/src/azure-cli-core/azure/cli/core/aaz/__init__.py index d340af29d64..23ae61bf70a 100644 --- a/src/azure-cli-core/azure/cli/core/aaz/__init__.py +++ b/src/azure-cli-core/azure/cli/core/aaz/__init__.py @@ -13,7 +13,7 @@ AAZFreeFormDictArg, AAZFloatArg, AAZBaseArg, AAZBoolArg, AAZListArg, AAZResourceGroupNameArg, \ AAZResourceLocationArg, AAZResourceIdArg, AAZSubscriptionIdArg, AAZUuidArg, AAZDateArg, AAZTimeArg, \ AAZDateTimeArg, AAZDurationArg, AAZFileArg, AAZPasswordArg, AAZPaginationTokenArg, AAZPaginationLimitArg, \ - AAZAnyTypeArg, AAZFileBytesArg + AAZAnyTypeArg, AAZFileUploadArg from ._arg_fmt import AAZStrArgFormat, AAZIntArgFormat, AAZFloatArgFormat, AAZBoolArgFormat, AAZObjectArgFormat, \ AAZDictArgFormat, AAZFreeFormDictArgFormat, AAZListArgFormat, AAZResourceLocationArgFormat, \ AAZResourceIdArgFormat, AAZSubscriptionIdArgFormat, AAZUuidFormat, AAZDateFormat, AAZTimeFormat, \ @@ -23,7 +23,7 @@ from ._command import AAZCommand, AAZWaitCommand, AAZCommandGroup, \ register_callback, register_command, register_command_group, load_aaz_command_table, link_helper from ._field_type import AAZIntType, AAZFloatType, AAZStrType, AAZBoolType, AAZDictType, AAZFreeFormDictType, \ - AAZListType, AAZObjectType, AAZIdentityObjectType, AAZAnyType, AAZBytesType + AAZListType, AAZObjectType, AAZIdentityObjectType, AAZAnyType, AAZFileUploadType from ._operation import AAZHttpOperation, AAZJsonInstanceUpdateOperation, AAZGenericInstanceUpdateOperation, \ AAZJsonInstanceDeleteOperation, AAZJsonInstanceCreateOperation from ._prompt import AAZPromptInput, AAZPromptPasswordInput diff --git a/src/azure-cli-core/azure/cli/core/aaz/_arg.py b/src/azure-cli-core/azure/cli/core/aaz/_arg.py index 95cac120edb..4806f20f46a 100644 --- a/src/azure-cli-core/azure/cli/core/aaz/_arg.py +++ b/src/azure-cli-core/azure/cli/core/aaz/_arg.py @@ -14,15 +14,16 @@ from knack.log import get_logger from ._arg_action import AAZSimpleTypeArgAction, AAZObjectArgAction, AAZDictArgAction, \ - AAZListArgAction, AAZGenericUpdateAction, AAZGenericUpdateForceStringAction, AAZAnyTypeArgAction + AAZListArgAction, AAZGenericUpdateAction, AAZGenericUpdateForceStringAction, AAZAnyTypeArgAction, \ + AAZFileUploadTypeArgAction from ._base import AAZBaseType, AAZUndefined from ._field_type import AAZObjectType, AAZStrType, AAZIntType, AAZBoolType, AAZFloatType, AAZListType, AAZDictType, \ - AAZSimpleType, AAZFreeFormDictType, AAZAnyType + AAZSimpleType, AAZFreeFormDictType, AAZAnyType, AAZFileUploadType from ._field_value import AAZObject from ._arg_fmt import AAZObjectArgFormat, AAZListArgFormat, AAZDictArgFormat, AAZFreeFormDictArgFormat, \ AAZSubscriptionIdArgFormat, AAZResourceLocationArgFormat, AAZResourceIdArgFormat, AAZUuidFormat, AAZDateFormat, \ AAZTimeFormat, AAZDateTimeFormat, AAZDurationFormat, AAZFileArgTextFormat, AAZPaginationTokenArgFormat, \ - AAZIntArgFormat, AAZFileBytesArgFormat + AAZIntArgFormat from .exceptions import AAZUnregisteredArg from ._prompt import AAZPromptInput @@ -599,16 +600,18 @@ def __init__(self, fmt=None, **kwargs): super().__init__(fmt=fmt, **kwargs) -class AAZFileBytesArg(AAZStrArg): - """Argument that accepts a file path and returns both file content in bytes""" - - def __init__(self, fmt=None, **kwargs): - fmt = fmt or AAZFileBytesArgFormat() - super().__init__(fmt=fmt, **kwargs) - +class AAZFileUploadArg(AAZStrArg, AAZFileUploadType): + """Argument that accepts a file path and returns file content, file hander and file size""" + @property def _type_in_help(self): - return "File Bytes" + return "File Content" + + def _build_cmd_action(self): + class Action(AAZFileUploadTypeArgAction): + _schema = self # bind action class with current schema + + return Action # Generic Update arguments diff --git a/src/azure-cli-core/azure/cli/core/aaz/_arg_action.py b/src/azure-cli-core/azure/cli/core/aaz/_arg_action.py index 5537ee3766b..d15518ec67f 100644 --- a/src/azure-cli-core/azure/cli/core/aaz/_arg_action.py +++ b/src/azure-cli-core/azure/cli/core/aaz/_arg_action.py @@ -249,6 +249,33 @@ def format_data(cls, data): return data +class AAZFileUploadTypeArgAction(AAZArgAction): + + @classmethod + def setup_operations(cls, dest_ops, values): + if values is None: + data = AAZBlankArgValue # use blank data when values string is None + else: + data = values + data = cls.format_data(data) + dest_ops.add(data) + + @classmethod + def format_data(cls, data): + if data == AAZBlankArgValue: + if cls._schema._blank == AAZUndefined: + raise AAZInvalidValueError("argument value cannot be blank") + data = copy.deepcopy(cls._schema._blank) + + if data is None: + if cls._schema._nullable: + return data + raise AAZInvalidValueError("field is not nullable") + if not os.path.exists(data): + raise AAZInvalidValueError(f"File '{data}' doesn't exist") + return data + + class AAZCompoundTypeArgAction(AAZArgAction): # pylint: disable=abstract-method @classmethod diff --git a/src/azure-cli-core/azure/cli/core/aaz/_arg_fmt.py b/src/azure-cli-core/azure/cli/core/aaz/_arg_fmt.py index 77ba87fb45f..cb46c2bf08a 100644 --- a/src/azure-cli-core/azure/cli/core/aaz/_arg_fmt.py +++ b/src/azure-cli-core/azure/cli/core/aaz/_arg_fmt.py @@ -747,29 +747,7 @@ def read_file(self, file_path): data = str(base64_data) return data - -class AAZFileBytesArgFormat(AAZBaseArgFormat): - """Format for file info argument that returns both filename and content""" - def __call__(self, ctx, value): - assert isinstance(value, AAZSimpleValue) - data = value._data - if data == AAZUndefined or data is None or value._is_patch: - return value - - assert isinstance(data, str) - - if not os.path.isfile(data): - raise AAZInvalidArgValueError(f"File '{data}' doesn't exist") - - try: - with open(data, 'rb') as f: - value._data = f.read() - return value - except Exception as e: - raise AAZInvalidArgValueError(f"Failed to read file '{data}': {str(e)}") - - class AAZPaginationTokenArgFormat(AAZBaseArgFormat): def __call__(self, ctx, value): def validate_json(s): diff --git a/src/azure-cli-core/azure/cli/core/aaz/_field_type.py b/src/azure-cli-core/azure/cli/core/aaz/_field_type.py index 5cdc375ce2d..2d615eb34ec 100644 --- a/src/azure-cli-core/azure/cli/core/aaz/_field_type.py +++ b/src/azure-cli-core/azure/cli/core/aaz/_field_type.py @@ -9,7 +9,7 @@ from azure.cli.core.util import shell_safe_json_parse from ._base import AAZBaseType, AAZValuePatch, AAZUndefined from ._field_value import AAZObject, AAZDict, AAZFreeFormDict, AAZList, AAZSimpleValue, \ - AAZIdentityObject, AAZAnyValue + AAZIdentityObject, AAZAnyValue, AAZFileUploadValue from ._utils import to_snack_case from .exceptions import AAZUnknownFieldError, AAZConflictFieldDefinitionError, AAZValuePrecisionLossError, \ AAZInvalidFieldError, AAZInvalidValueError @@ -115,6 +115,10 @@ class AAZBytesType(AAZSimpleType): DataType = bytes +class AAZFileUploadType(AAZStrType): + _ValueCls = AAZFileUploadValue + + class AAZAnyType(AAZSimpleType): """Any type""" diff --git a/src/azure-cli-core/azure/cli/core/aaz/_field_value.py b/src/azure-cli-core/azure/cli/core/aaz/_field_value.py index 69857bd61a6..f8bc5a58aa8 100644 --- a/src/azure-cli-core/azure/cli/core/aaz/_field_value.py +++ b/src/azure-cli-core/azure/cli/core/aaz/_field_value.py @@ -5,6 +5,7 @@ # pylint: disable=protected-access from ._base import AAZBaseValue, AAZValuePatch, AAZUndefined import abc +import os class AAZSimpleValue(AAZBaseValue): @@ -59,6 +60,23 @@ class AAZAnyValue(AAZSimpleValue): # pylint: disable=too-few-public-methods pass +class AAZFileUploadValue(AAZSimpleValue): + + def to_serialized_data(self, **kwargs): + _file_size = os.path.getsize(self._data) + _content = None + _file_handle = None + + _file_handle = open(self._data, "rb") + if _file_size < 5 * 1024 * 1024: # 5MB + _content = _file_handle.read() + _file_handle.close() + _file_handle = None + return _content, _file_handle, _file_size + else: + return _content, _file_handle, _file_size + + class AAZObject(AAZBaseValue): def __init__(self, schema, data): From 583153a12ef33317a26bbda2b46ad95fb3cbde69 Mon Sep 17 00:00:00 2001 From: AllyW Date: Tue, 3 Jun 2025 08:51:13 +0800 Subject: [PATCH 4/6] refine --- src/azure-cli-core/azure/cli/core/aaz/_arg_fmt.py | 2 +- src/azure-cli-core/azure/cli/core/aaz/_field_type.py | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/aaz/_arg_fmt.py b/src/azure-cli-core/azure/cli/core/aaz/_arg_fmt.py index cb46c2bf08a..42d26cf6545 100644 --- a/src/azure-cli-core/azure/cli/core/aaz/_arg_fmt.py +++ b/src/azure-cli-core/azure/cli/core/aaz/_arg_fmt.py @@ -747,7 +747,7 @@ def read_file(self, file_path): data = str(base64_data) return data - + class AAZPaginationTokenArgFormat(AAZBaseArgFormat): def __call__(self, ctx, value): def validate_json(s): diff --git a/src/azure-cli-core/azure/cli/core/aaz/_field_type.py b/src/azure-cli-core/azure/cli/core/aaz/_field_type.py index 2d615eb34ec..aa94d2a3c24 100644 --- a/src/azure-cli-core/azure/cli/core/aaz/_field_type.py +++ b/src/azure-cli-core/azure/cli/core/aaz/_field_type.py @@ -111,10 +111,6 @@ def process_data(self, data, **kwargs): return data -class AAZBytesType(AAZSimpleType): - DataType = bytes - - class AAZFileUploadType(AAZStrType): _ValueCls = AAZFileUploadValue From 861ef69a53285e153d6f88b97a59a7957e96b6b0 Mon Sep 17 00:00:00 2001 From: AllyW Date: Wed, 4 Jun 2025 10:32:34 +0800 Subject: [PATCH 5/6] fix style --- src/azure-cli-core/azure/cli/core/aaz/_field_value.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/aaz/_field_value.py b/src/azure-cli-core/azure/cli/core/aaz/_field_value.py index f8bc5a58aa8..bfae60a9300 100644 --- a/src/azure-cli-core/azure/cli/core/aaz/_field_value.py +++ b/src/azure-cli-core/azure/cli/core/aaz/_field_value.py @@ -65,8 +65,6 @@ class AAZFileUploadValue(AAZSimpleValue): def to_serialized_data(self, **kwargs): _file_size = os.path.getsize(self._data) _content = None - _file_handle = None - _file_handle = open(self._data, "rb") if _file_size < 5 * 1024 * 1024: # 5MB _content = _file_handle.read() From 953046772870e4098adff7e7f1701db280c500c3 Mon Sep 17 00:00:00 2001 From: AllyW Date: Wed, 4 Jun 2025 11:25:53 +0800 Subject: [PATCH 6/6] fix style --- src/azure-cli-core/azure/cli/core/aaz/_arg_action.py | 2 +- src/azure-cli-core/azure/cli/core/aaz/_field_value.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/aaz/_arg_action.py b/src/azure-cli-core/azure/cli/core/aaz/_arg_action.py index d15518ec67f..7933468f30d 100644 --- a/src/azure-cli-core/azure/cli/core/aaz/_arg_action.py +++ b/src/azure-cli-core/azure/cli/core/aaz/_arg_action.py @@ -252,7 +252,7 @@ def format_data(cls, data): class AAZFileUploadTypeArgAction(AAZArgAction): @classmethod - def setup_operations(cls, dest_ops, values): + def setup_operations(cls, dest_ops, values, prefix_keys=None): if values is None: data = AAZBlankArgValue # use blank data when values string is None else: diff --git a/src/azure-cli-core/azure/cli/core/aaz/_field_value.py b/src/azure-cli-core/azure/cli/core/aaz/_field_value.py index bfae60a9300..407540d9808 100644 --- a/src/azure-cli-core/azure/cli/core/aaz/_field_value.py +++ b/src/azure-cli-core/azure/cli/core/aaz/_field_value.py @@ -60,9 +60,9 @@ class AAZAnyValue(AAZSimpleValue): # pylint: disable=too-few-public-methods pass -class AAZFileUploadValue(AAZSimpleValue): +class AAZFileUploadValue(AAZSimpleValue): # pylint: disable=too-few-public-methods - def to_serialized_data(self, **kwargs): + def to_serialized_data(self, processor=None, **kwargs): _file_size = os.path.getsize(self._data) _content = None _file_handle = open(self._data, "rb") @@ -71,8 +71,8 @@ def to_serialized_data(self, **kwargs): _file_handle.close() _file_handle = None return _content, _file_handle, _file_size - else: - return _content, _file_handle, _file_size + + return _content, _file_handle, _file_size class AAZObject(AAZBaseValue):