Skip to content

Commit fe7e4c8

Browse files
authored
[Core] aaz: Support AAZDurationArg, AAZDateArg, AAZTimeArg, AAZDateTimeArg and AAZUuidArg (#23280)
* support AAZUuidArg and AAZUuidFormat * support AAZDurationArg, AAZDateArg, AAZTimeArg and AAZDateTimeArg * fix test * fix style issue
1 parent f809377 commit fe7e4c8

File tree

4 files changed

+540
-3
lines changed

4 files changed

+540
-3
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
@@ -11,9 +11,10 @@
1111

1212
from ._arg import has_value, AAZArgumentsSchema, AAZArgEnum, AAZStrArg, AAZIntArg, AAZObjectArg, AAZDictArg, \
1313
AAZFloatArg, AAZBaseArg, AAZBoolArg, AAZListArg, AAZResourceGroupNameArg, AAZResourceLocationArg, \
14-
AAZResourceIdArg, AAZSubscriptionIdArg
14+
AAZResourceIdArg, AAZSubscriptionIdArg, AAZUuidArg, AAZDateArg, AAZTimeArg, AAZDateTimeArg, AAZDurationArg
1515
from ._arg_fmt import AAZStrArgFormat, AAZIntArgFormat, AAZFloatArgFormat, AAZBoolArgFormat, AAZObjectArgFormat, \
16-
AAZDictArgFormat, AAZListArgFormat, AAZResourceLocationArgFormat, AAZResourceIdArgFormat, AAZSubscriptionIdArgFormat
16+
AAZDictArgFormat, AAZListArgFormat, AAZResourceLocationArgFormat, AAZResourceIdArgFormat, \
17+
AAZSubscriptionIdArgFormat, AAZUuidFormat, AAZDateFormat, AAZTimeFormat, AAZDateTimeFormat, AAZDurationFormat
1718
from ._base import AAZValuePatch, AAZUndefined
1819
from ._command import AAZCommand, AAZWaitCommand, AAZCommandGroup, \
1920
register_command, register_command_group, load_aaz_command_table

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

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
AAZSimpleType
1515
from ._field_value import AAZObject
1616
from ._arg_fmt import AAZObjectArgFormat, AAZListArgFormat, AAZDictArgFormat, AAZSubscriptionIdArgFormat, \
17-
AAZResourceLocationArgFormat, AAZResourceIdArgFormat
17+
AAZResourceLocationArgFormat, AAZResourceIdArgFormat, AAZUuidFormat, AAZDateFormat, AAZTimeFormat, \
18+
AAZDateTimeFormat, AAZDurationFormat
1819

1920
# pylint: disable=redefined-builtin, protected-access
2021

@@ -166,6 +167,61 @@ def _type_in_help(self):
166167
return "String"
167168

168169

170+
class AAZDurationArg(AAZStrArg):
171+
172+
def __init__(self, fmt=None, **kwargs):
173+
fmt = fmt or AAZDurationFormat()
174+
super().__init__(fmt=fmt, **kwargs)
175+
176+
@property
177+
def _type_in_help(self):
178+
return "Duration"
179+
180+
181+
class AAZDateArg(AAZStrArg):
182+
183+
def __init__(self, fmt=None, **kwargs):
184+
fmt = fmt or AAZDateFormat()
185+
super().__init__(fmt=fmt, **kwargs)
186+
187+
@property
188+
def _type_in_help(self):
189+
return "Date"
190+
191+
192+
class AAZTimeArg(AAZStrArg):
193+
194+
def __init__(self, fmt=None, **kwargs):
195+
fmt = fmt or AAZTimeFormat()
196+
super().__init__(fmt=fmt, **kwargs)
197+
198+
@property
199+
def _type_in_help(self):
200+
return "Time"
201+
202+
203+
class AAZDateTimeArg(AAZStrArg):
204+
205+
def __init__(self, fmt=None, **kwargs):
206+
fmt = fmt or AAZDateTimeFormat()
207+
super().__init__(fmt=fmt, **kwargs)
208+
209+
@property
210+
def _type_in_help(self):
211+
return "DateTime"
212+
213+
214+
class AAZUuidArg(AAZStrArg):
215+
216+
def __init__(self, fmt=None, **kwargs):
217+
fmt = fmt or AAZUuidFormat()
218+
super().__init__(fmt=fmt, **kwargs)
219+
220+
@property
221+
def _type_in_help(self):
222+
return "GUID/UUID"
223+
224+
169225
class AAZIntArg(AAZSimpleTypeArg, AAZIntType):
170226

171227
@property

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

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,220 @@ def __call__(self, ctx, value):
5656
return value
5757

5858

59+
class AAZDurationFormat(AAZBaseArgFormat):
60+
61+
def __call__(self, ctx, value):
62+
assert isinstance(value, AAZSimpleValue)
63+
data = value._data
64+
if data == AAZUndefined or data is None or value._is_patch:
65+
return value
66+
67+
assert isinstance(data, str)
68+
from msrest.serialization import Serializer
69+
from isodate.isoerror import ISO8601Error
70+
71+
try:
72+
data = Serializer.serialize_duration(data.upper())
73+
except ISO8601Error:
74+
try:
75+
# parse '##DT##H##M##S
76+
data = Serializer.serialize_duration(f'P0M{data.upper()}')
77+
except ISO8601Error:
78+
try:
79+
# parse '##H##M##S'
80+
data = Serializer.serialize_duration(f'PT{data.upper()}')
81+
except ISO8601Error:
82+
raise AAZInvalidArgValueError(
83+
f"Invalid format: '{data}' should be of the form "
84+
f"'##dT##h##m##s', '##h##m##s' or ISO8601 duration"
85+
)
86+
value._data = data
87+
return value
88+
89+
90+
class AAZDateFormat(AAZBaseArgFormat):
91+
92+
def __init__(self):
93+
self.help_string = 'Format: date (yyyy-mm-dd)'
94+
95+
def __call__(self, ctx, value):
96+
assert isinstance(value, AAZSimpleValue)
97+
data = value._data
98+
if data == AAZUndefined or data is None or value._is_patch:
99+
return value
100+
101+
assert isinstance(data, str)
102+
import dateutil.parser
103+
import dateutil.tz
104+
105+
dt_val = None
106+
try:
107+
dt_val = dateutil.parser.parse(data)
108+
except ValueError:
109+
pass
110+
111+
if not dt_val:
112+
raise AAZInvalidArgValueError(
113+
f"Unable to parse: '{data}'. Expected format: {self.help_string}"
114+
)
115+
116+
if any([dt_val.hour, dt_val.minute, dt_val.second, dt_val.microsecond]):
117+
logger.warning('Time info will be ignored in %s.', data)
118+
119+
if dt_val.tzinfo:
120+
logger.warning('Timezone info will be ignored in %s.', data)
121+
122+
data = "{:04}-{:02}-{:02}".format(dt_val.year, dt_val.month, dt_val.day)
123+
value._data = data
124+
return value
125+
126+
127+
class AAZTimeFormat(AAZBaseArgFormat):
128+
129+
def __init__(self):
130+
self.help_string = 'Format: time (hh:mm:ss.xxxxxx)'
131+
132+
def __call__(self, ctx, value):
133+
assert isinstance(value, AAZSimpleValue)
134+
data = value._data
135+
if data == AAZUndefined or data is None or value._is_patch:
136+
return value
137+
138+
assert isinstance(data, str)
139+
import dateutil.parser
140+
import dateutil.tz
141+
142+
dt_val = None
143+
try:
144+
dt_val = dateutil.parser.parse(data)
145+
except ValueError:
146+
pass
147+
148+
if not dt_val:
149+
raise AAZInvalidArgValueError(
150+
f"Unable to parse: '{data}'. Expected format: {self.help_string}"
151+
)
152+
153+
if any([dt_val.day, dt_val.month, dt_val.year]):
154+
logger.warning('Date info will be ignored in %s.', data)
155+
156+
if dt_val.tzinfo:
157+
logger.warning('Timezone info will be ignored in %s.', data)
158+
159+
data = "{:02}:{:02}:{:02}".format(dt_val.hour, dt_val.minute, dt_val.second)
160+
if dt_val.microsecond:
161+
microseconds = str(dt_val.microsecond).rjust(6, '0').rstrip('0').ljust(3, '0')
162+
data += '.' + microseconds
163+
164+
value._data = data
165+
return value
166+
167+
168+
class AAZDateTimeFormat(AAZBaseArgFormat):
169+
170+
def __init__(self, protocol="iso"):
171+
self.protocol = protocol
172+
self.help_string = 'Format: date (yyyy-mm-dd) time (hh:mm:ss.xxxxxx) timezone (+/-hh:mm)'
173+
174+
def __call__(self, ctx, value):
175+
assert isinstance(value, AAZSimpleValue)
176+
data = value._data
177+
if data == AAZUndefined or data is None or value._is_patch:
178+
return value
179+
180+
assert isinstance(data, str)
181+
import dateutil.parser
182+
import dateutil.tz
183+
from msrest.serialization import Serializer
184+
185+
dt_val = None
186+
try:
187+
dt_val = dateutil.parser.parse(data)
188+
except ValueError:
189+
pass
190+
191+
if not dt_val:
192+
raise AAZInvalidArgValueError(
193+
f"Unable to parse: '{data}'. Expected format: {self.help_string}"
194+
)
195+
196+
if not dt_val.tzinfo:
197+
dt_val = dt_val.replace(tzinfo=dateutil.tz.tzlocal())
198+
199+
if self.protocol == "iso":
200+
data = Serializer.serialize_iso(dt_val)
201+
elif self.protocol == "rfc":
202+
data = Serializer.serialize_rfc(dt_val)
203+
else:
204+
raise NotImplementedError()
205+
206+
value._data = data
207+
return value
208+
209+
210+
class AAZUuidFormat(AAZBaseArgFormat):
211+
212+
_uuid_pattern = re.compile(r'^[{(]?[0-9a-fA-F]{8}([-]?[0-9a-fA-F]{4}){3}[-]?[0-9a-fA-F]{12}[)}]?$')
213+
214+
def __init__(self, case=None, braces_removed=True, hyphens_filled=True):
215+
"""
216+
:param case: 'upper' to format data into upper case, 'lower' to format data into lower case
217+
"""
218+
self.case = case
219+
self.braces_removed = braces_removed
220+
self.hyphens_filled = hyphens_filled
221+
222+
def __call__(self, ctx, value):
223+
assert isinstance(value, AAZSimpleValue)
224+
data = value._data
225+
if data == AAZUndefined or data is None or value._is_patch:
226+
return value
227+
228+
assert isinstance(data, str)
229+
if not self._uuid_pattern.fullmatch(data):
230+
raise AAZInvalidArgValueError(
231+
f"Invalid format: '{data}' is not a valid GUID or UUID"
232+
)
233+
234+
if '-' in data and data.count('-') != 4:
235+
raise AAZInvalidArgValueError(
236+
f"Invalid format: '{data}' is not a valid GUID or UUID"
237+
)
238+
239+
if data.startswith('{') or data.endswith('}'):
240+
# remove braces
241+
if not (data.startswith('{') and data.endswith('}')):
242+
raise AAZInvalidArgValueError(
243+
f"Invalid format: '{data}' is not a valid GUID or UUID"
244+
)
245+
if self.braces_removed:
246+
data = data[1:-1]
247+
248+
elif data.startswith('(') or data.endswith(')'):
249+
# remove parentheses
250+
if not (data.startswith('(') and data.endswith(')')):
251+
raise AAZInvalidArgValueError(
252+
f"Invalid format: '{data}' is not a valid GUID or UUID"
253+
)
254+
if self.braces_removed:
255+
data = data[1:-1]
256+
257+
if '-' not in data and self.hyphens_filled:
258+
# add '-' in data
259+
if data[0] in ('{', '('):
260+
data = f'{data[:9]}-{data[9:13]}-{data[13:17]}-{data[17:21]}-{data[21:]}'
261+
else:
262+
data = f'{data[:8]}-{data[8:12]}-{data[12:16]}-{data[16:20]}-{data[20:]}'
263+
264+
if self.case == 'upper':
265+
data = data.upper()
266+
elif self.case == 'lower':
267+
data = data.lower()
268+
269+
value._data = data
270+
return value
271+
272+
59273
class AAZIntArgFormat(AAZBaseArgFormat):
60274

61275
def __init__(self, multiple_of=None, maximum=None, minimum=None):

0 commit comments

Comments
 (0)