Skip to content

Commit 69c75ba

Browse files
committed
Support AnyType
1 parent 57a02bc commit 69c75ba

File tree

10 files changed

+520
-210
lines changed

10 files changed

+520
-210
lines changed

src/azure-cli-core/azure/cli/core/aaz/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
from ._arg import AAZArgumentsSchema, AAZArgEnum, AAZStrArg, AAZIntArg, AAZObjectArg, AAZDictArg, \
1313
AAZFreeFormDictArg, AAZFloatArg, AAZBaseArg, AAZBoolArg, AAZListArg, AAZResourceGroupNameArg, \
1414
AAZResourceLocationArg, AAZResourceIdArg, AAZSubscriptionIdArg, AAZUuidArg, AAZDateArg, AAZTimeArg, \
15-
AAZDateTimeArg, AAZDurationArg, AAZFileArg, AAZPasswordArg, AAZPaginationTokenArg, AAZPaginationLimitArg
15+
AAZDateTimeArg, AAZDurationArg, AAZFileArg, AAZPasswordArg, AAZPaginationTokenArg, AAZPaginationLimitArg, \
16+
AAZAnyTypeArg
1617
from ._arg_fmt import AAZStrArgFormat, AAZIntArgFormat, AAZFloatArgFormat, AAZBoolArgFormat, AAZObjectArgFormat, \
1718
AAZDictArgFormat, AAZFreeFormDictArgFormat, AAZListArgFormat, AAZResourceLocationArgFormat, \
1819
AAZResourceIdArgFormat, AAZSubscriptionIdArgFormat, AAZUuidFormat, AAZDateFormat, AAZTimeFormat, \
@@ -22,7 +23,7 @@
2223
from ._command import AAZCommand, AAZWaitCommand, AAZCommandGroup, \
2324
register_callback, register_command, register_command_group, load_aaz_command_table, link_helper
2425
from ._field_type import AAZIntType, AAZFloatType, AAZStrType, AAZBoolType, AAZDictType, AAZFreeFormDictType, \
25-
AAZListType, AAZObjectType, AAZIdentityObjectType
26+
AAZListType, AAZObjectType, AAZIdentityObjectType, AAZAnyType
2627
from ._operation import AAZHttpOperation, AAZJsonInstanceUpdateOperation, AAZGenericInstanceUpdateOperation, \
2728
AAZJsonInstanceDeleteOperation, AAZJsonInstanceCreateOperation
2829
from ._prompt import AAZPromptInput, AAZPromptPasswordInput

src/azure-cli-core/azure/cli/core/aaz/_arg.py

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
from knack.util import status_tag_messages
1414
from knack.log import get_logger
1515

16-
from ._arg_action import AAZSimpleTypeArgAction, AAZObjectArgAction, AAZDictArgAction, AAZFreeFormDictArgAction, \
17-
AAZListArgAction, AAZGenericUpdateAction, AAZGenericUpdateForceStringAction
16+
from ._arg_action import AAZSimpleTypeArgAction, AAZObjectArgAction, AAZDictArgAction, \
17+
AAZListArgAction, AAZGenericUpdateAction, AAZGenericUpdateForceStringAction, AAZAnyTypeArgAction
1818
from ._base import AAZBaseType, AAZUndefined
1919
from ._field_type import AAZObjectType, AAZStrType, AAZIntType, AAZBoolType, AAZFloatType, AAZListType, AAZDictType, \
20-
AAZSimpleType, AAZFreeFormDictType
20+
AAZSimpleType, AAZFreeFormDictType, AAZAnyType
2121
from ._field_value import AAZObject
2222
from ._arg_fmt import AAZObjectArgFormat, AAZListArgFormat, AAZDictArgFormat, AAZFreeFormDictArgFormat, \
2323
AAZSubscriptionIdArgFormat, AAZResourceLocationArgFormat, AAZResourceIdArgFormat, AAZUuidFormat, AAZDateFormat, \
@@ -356,6 +356,28 @@ def _type_in_help(self):
356356
return "Float"
357357

358358

359+
class AAZAnyTypeArg(AAZBaseArg, AAZAnyType):
360+
361+
def _build_cmd_action(self):
362+
class Action(AAZAnyTypeArgAction):
363+
_schema = self # bind action class with current schema
364+
return Action
365+
366+
def to_cmd_arg(self, name, **kwargs):
367+
from ._help import shorthand_help_messages
368+
arg = super().to_cmd_arg(name, **kwargs)
369+
short_summary = arg.type.settings.get('help', None) or ''
370+
if short_summary:
371+
short_summary += ' '
372+
short_summary += shorthand_help_messages['short-summary-anytype']
373+
arg.help = short_summary
374+
return arg
375+
376+
@property
377+
def _type_in_help(self):
378+
return "Any"
379+
380+
359381
class AAZCompoundTypeArg(AAZBaseArg):
360382

361383
@abc.abstractmethod
@@ -427,30 +449,15 @@ def _type_in_help(self):
427449
return f"Dict<String,{self.Element._type_in_help}>"
428450

429451

430-
class AAZFreeFormDictArg(AAZBaseArg, AAZFreeFormDictType):
452+
# Warning: This type should not be used any more, the new aaz-dev-tools only use AAZDictType with AAZAnyType
453+
class AAZFreeFormDictArg(AAZDictArg, AAZFreeFormDictType):
431454

432455
def __init__(self, fmt=None, **kwargs):
433456
fmt = fmt or AAZFreeFormDictArgFormat()
434457
super().__init__(fmt=fmt, **kwargs)
435-
436-
def to_cmd_arg(self, name, **kwargs):
437-
arg = super().to_cmd_arg(name, **kwargs)
438-
short_summary = arg.type.settings.get('help', None) or ''
439-
if short_summary:
440-
short_summary += ' '
441-
short_summary += "Support json-file and yaml-file."
442-
arg.help = short_summary
443-
return arg
444-
445-
def _build_cmd_action(self):
446-
class Action(AAZFreeFormDictArgAction):
447-
_schema = self # bind action class with current schema
448-
449-
return Action
450-
451-
@property
452-
def _type_in_help(self):
453-
return "Dict<String, Any>"
458+
# for backward compatible, support nullable value here for AAZFreeFormDictArg,
459+
# from the new code gen tools, it will avoid using AAZFreeFormDictArg
460+
self._element = AAZAnyTypeArg(nullable=True)
454461

455462

456463
class AAZListArg(AAZCompoundTypeArg, AAZListType):

src/azure-cli-core/azure/cli/core/aaz/_arg_action.py

Lines changed: 79 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,14 @@ def format_data(cls, data):
150150
return AAZPromptInputOperation(prompt=cls._schema._blank, action_cls=cls)
151151
data = copy.deepcopy(cls._schema._blank)
152152

153+
if data is None:
154+
if cls._schema._nullable:
155+
return data
156+
raise AAZInvalidValueError("field is not nullable")
157+
158+
if cls._schema.DataType == str and isinstance(data, (int, bool, float)):
159+
data = str(data).lower() # convert to json raw values
160+
153161
if isinstance(data, str):
154162
# transfer string into correct data
155163
if cls._schema.enum:
@@ -159,12 +167,80 @@ def format_data(cls, data):
159167
if isinstance(data, cls._schema.DataType):
160168
return data
161169

170+
raise AAZInvalidValueError(f"{cls._schema.DataType} type value expected, got '{data}'({type(data)})")
171+
172+
173+
class AAZAnyTypeArgAction(AAZArgAction):
174+
175+
@classmethod
176+
def setup_operations(cls, dest_ops, values, prefix_keys=None):
177+
if prefix_keys is None:
178+
prefix_keys = []
179+
if values is None:
180+
data = AAZBlankArgValue # use blank data when values string is None
181+
else:
182+
if isinstance(values, list):
183+
assert prefix_keys # the values will be input as an list when parse singular option of a list argument
184+
if len(values) != 1:
185+
raise AAZInvalidValueError(f"only support 1 value, got {len(values)}: {values}")
186+
values = values[0]
187+
188+
if isinstance(values, str) and len(values) > 0:
189+
try:
190+
data = cls.decode_str(values)
191+
except AAZShowHelp as aaz_help:
192+
aaz_help.schema = cls._schema
193+
raise aaz_help
194+
else:
195+
data = values
196+
data = cls.format_data(data)
197+
dest_ops.add(data, *prefix_keys)
198+
199+
@classmethod
200+
def decode_str(cls, value):
201+
from azure.cli.core.util import get_file_json, shell_safe_json_parse, get_file_yaml
202+
203+
# check if the value is a partial value
204+
key, _, v = cls._str_parser.split_partial_value(value)
205+
if key is not None:
206+
raise AAZInvalidValueError(f"AnyType args only support full value shorthand syntax, please don't use partial value shorthand syntax. If it's a simple string, please wrap it with single quotes.")
207+
208+
# read from file
209+
path = os.path.expanduser(value)
210+
if os.path.exists(path):
211+
if path.endswith('.yml') or path.endswith('.yaml'):
212+
# read from yaml file
213+
v = get_file_yaml(path)
214+
else:
215+
# read from json file
216+
v = get_file_json(path, preserve_order=True)
217+
else:
218+
try:
219+
v = cls._str_parser(value)
220+
except AAZInvalidShorthandSyntaxError as shorthand_ex:
221+
try:
222+
v = shell_safe_json_parse(value, True)
223+
except Exception as ex:
224+
logger.debug(ex) # log parse json failed expression
225+
raise shorthand_ex # raise shorthand syntax exception
226+
return v
227+
228+
@classmethod
229+
def format_data(cls, data):
230+
if data == AAZBlankArgValue:
231+
if cls._schema._blank == AAZUndefined:
232+
raise AAZInvalidValueError("argument value cannot be blank")
233+
if isinstance(cls._schema._blank, AAZPromptInput):
234+
# Postpone the prompt input when apply the operation.
235+
# In order not to break the logic of displaying help or validating other parameters.
236+
return AAZPromptInputOperation(prompt=cls._schema._blank, action_cls=cls)
237+
data = copy.deepcopy(cls._schema._blank)
238+
162239
if data is None:
163240
if cls._schema._nullable:
164241
return data
165242
raise AAZInvalidValueError("field is not nullable")
166-
167-
raise AAZInvalidValueError(f"{cls._schema.DataType} type value expected, got '{data}'({type(data)})")
243+
return data
168244

169245

170246
class AAZCompoundTypeArgAction(AAZArgAction): # pylint: disable=abstract-method
@@ -245,7 +321,7 @@ def _decode_value(cls, schema, value): # pylint: disable=unused-argument
245321
# simple type
246322
v = cls._str_parser(value, is_simple=True)
247323
else:
248-
# compound type
324+
# compound type or any type
249325
# read from file
250326
path = os.path.expanduser(value)
251327
if os.path.exists(path):
@@ -264,7 +340,6 @@ def _decode_value(cls, schema, value): # pylint: disable=unused-argument
264340
except Exception as ex:
265341
logger.debug(ex) # log parse json failed expression
266342
raise shorthand_ex # raise shorthand syntax exception
267-
268343
return v
269344

270345

@@ -324,52 +399,6 @@ def format_data(cls, data):
324399
raise AAZInvalidValueError(f"dict type value expected, got '{data}'({type(data)})")
325400

326401

327-
class AAZFreeFormDictArgAction(AAZSimpleTypeArgAction):
328-
329-
@classmethod
330-
def decode_str(cls, value):
331-
from azure.cli.core.util import get_file_json, shell_safe_json_parse, get_file_yaml
332-
333-
if len(value) == 0:
334-
# the express "a=" will return the blank value of schema 'a'
335-
return AAZBlankArgValue
336-
337-
path = os.path.expanduser(value)
338-
if os.path.exists(path):
339-
if path.endswith('.yml') or path.endswith('.yaml'):
340-
# read from yaml file
341-
v = get_file_yaml(path)
342-
else:
343-
# read from json file
344-
v = get_file_json(path, preserve_order=True)
345-
else:
346-
try:
347-
v = shell_safe_json_parse(value, True)
348-
except Exception as ex:
349-
logger.debug(ex) # log parse json failed expression
350-
raise
351-
return v
352-
353-
@classmethod
354-
def format_data(cls, data):
355-
if data == AAZBlankArgValue:
356-
if cls._schema._blank == AAZUndefined:
357-
raise AAZInvalidValueError("argument value cannot be blank")
358-
assert not isinstance(cls._schema._blank, AAZPromptInput), "Prompt input is not supported in " \
359-
"FreeFormDict args."
360-
data = copy.deepcopy(cls._schema._blank)
361-
362-
if isinstance(data, dict):
363-
return data
364-
365-
if data is None:
366-
if cls._schema._nullable:
367-
return data
368-
raise AAZInvalidValueError("field is not nullable")
369-
370-
raise AAZInvalidValueError(f"dict type value expected, got '{data}'({type(data)})")
371-
372-
373402
class AAZListArgAction(AAZCompoundTypeArgAction):
374403

375404
def __call__(self, parser, namespace, values, option_string=None):

src/azure-cli-core/azure/cli/core/aaz/_arg_fmt.py

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -466,32 +466,9 @@ def __call__(self, ctx, value):
466466
return value
467467

468468

469-
class AAZFreeFormDictArgFormat(AAZBaseArgFormat):
470-
471-
def __init__(self, max_properties=None, min_properties=None):
472-
self._max_properties = max_properties
473-
self._min_properties = min_properties
474-
475-
def __call__(self, ctx, value):
476-
assert isinstance(value, AAZFreeFormDict)
477-
data = value._data
478-
if data == AAZUndefined or data is None:
479-
return value
480-
481-
assert isinstance(data, dict)
482-
483-
if value._is_patch:
484-
return value
485-
486-
if self._min_properties and len(value) < self._min_properties:
487-
raise AAZInvalidArgValueError(
488-
f"Invalid format: dict length is less than {self._min_properties}")
489-
490-
if self._max_properties and len(value) > self._max_properties:
491-
raise AAZInvalidArgValueError(
492-
f"Invalid format: dict length is greater than {self._max_properties}")
493-
494-
return value
469+
# Warning: This type should not be used any more, the new aaz-dev-tools only use AAZDictArgFormat
470+
class AAZFreeFormDictArgFormat(AAZDictArgFormat):
471+
pass
495472

496473

497474
class AAZListArgFormat(AAZBaseArgFormat):

src/azure-cli-core/azure/cli/core/aaz/_field_type.py

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from azure.cli.core.util import shell_safe_json_parse
1010
from ._base import AAZBaseType, AAZValuePatch, AAZUndefined
1111
from ._field_value import AAZObject, AAZDict, AAZFreeFormDict, AAZList, AAZSimpleValue, \
12-
AAZIdentityObject
12+
AAZIdentityObject, AAZAnyValue
1313
from ._utils import to_snack_case
1414
from .exceptions import AAZUnknownFieldError, AAZConflictFieldDefinitionError, AAZValuePrecisionLossError, \
1515
AAZInvalidFieldError, AAZInvalidValueError
@@ -111,6 +111,41 @@ def process_data(self, data, **kwargs):
111111
return data
112112

113113

114+
class AAZAnyType(AAZSimpleType):
115+
"""Any type"""
116+
117+
_ValueCls = AAZAnyValue
118+
119+
def process_data(self, data, **kwargs):
120+
if isinstance(data, (AAZObject, AAZDict, AAZList)):
121+
# convert them to simple dict, list, None or AAZUndefined values
122+
value = data.to_serialized_data()
123+
# TODO: may need to add warnings or raise error when treat the patch assign into the full assign.
124+
# for the code gen commands it will not happen as the any type args only support full assign.
125+
# but for the customization code, this will happen when convert AAZCompoundTypeArg to AAZAnyTypeArg
126+
# if data._is_patch and isinstance(value, (dict, list)) and len(value):
127+
# pass
128+
data = value
129+
130+
if data == None: # noqa: E711, pylint: disable=singleton-comparison
131+
# data can be None or AAZSimpleValue == None
132+
if self._nullable:
133+
return None
134+
return AAZValuePatch.build(self)
135+
136+
if data == AAZUndefined:
137+
return AAZValuePatch.build(self)
138+
139+
if isinstance(data, AAZSimpleValue):
140+
if data._is_patch:
141+
# return value patch
142+
return AAZValuePatch.build(self)
143+
144+
return data._data
145+
146+
return data
147+
148+
114149
# compound types
115150
class AAZObjectType(AAZBaseType):
116151
"""Object value type"""
@@ -355,14 +390,17 @@ def Element(self, value):
355390
def __getitem__(self, key):
356391
return self.Element
357392

358-
359-
class AAZFreeFormDictType(AAZBaseDictType):
393+
# Warning: This type should not be used any more, the new aaz-dev-tools only use AAZDictType with AAZAnyType
394+
class AAZFreeFormDictType(AAZDictType):
360395
"""Free form dict value type"""
361396

362397
_ValueCls = AAZFreeFormDict
363398

364-
def __getitem__(self, key):
365-
return None
399+
def __init__(self, *args, **kwargs):
400+
super().__init__(*args, **kwargs)
401+
# for backward compatible, so support nullable for any type.
402+
# from the new code gen tools, it will avoid using AAZFreeFormDictType
403+
self._element = AAZAnyType(nullable=True)
366404

367405

368406
class AAZListType(AAZBaseType):

0 commit comments

Comments
 (0)