Skip to content

Commit 47af269

Browse files
henryzdaiHenry DaiCopilot
authored andcommitted
[Microsoft.ChangeSafety][BugFix] fix additional data parsing issue (Azure#9901)
* Fix --additional-data and --change-definition to accept free-form nested JSON The --additional-data argument was defined as AAZObjectArg with no child fields, causing 'Model AAZObjectArg has no field named safeFly' errors. The --change-definition details field had the same issue, rejecting ApiOperations payloads with 'no field named operations'. Changes: - Change additional_data from AAZObjectArg to AAZFreeFormDictArg - Change change_definition.details from AAZObjectArg to AAZFreeFormDictArg - Change corresponding AAZObjectType to AAZFreeFormDictType in builders and response schemas across create, update, show, and list - Add content injection for additionalData in custom.py (same pattern as changeDefinition) to work around AAZ builder serialization limitation - Add tests for SafeFly payload, links, and orchestration-tool arguments - Bump version to 1.0.0b2 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Retrigger CI (transient GitHub 403 incident on prior run) * Address PR nits: use 'is None' / 'is not None' for additional_data checks Per review feedback, replace truthiness checks with explicit None checks so that an explicitly provided empty dict {} is treated as a valid user-supplied value rather than being silently dropped. - _inject_additional_data_into_content: 'if not additional_data' -> 'is None' - ChangeRecordCreate.pre_operations: 'if additional_data' -> 'is not None' - ChangeRecordUpdate.pre_operations: same fix for consistency (duplicated pattern) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Henry Dai <henrydai@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 6ba3886 commit 47af269

8 files changed

Lines changed: 249 additions & 37 deletions

File tree

src/azure-changesafety/HISTORY.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
Release History
44
===============
55

6+
1.0.0b2
7+
+++++++
8+
* Fix ``--additional-data`` argument to accept free-form nested JSON (e.g., SafeFly payloads).
9+
* Fix ``--change-definition`` details to accept free-form nested JSON (e.g., ApiOperations with operations array).
10+
* Inject ``additionalData`` into request body via content override (AAZ builder workaround).
11+
* Add content injection for ``additionalData`` in both Create and Update commands.
12+
613
1.0.0b1
714
+++++++
815
* Initial release.

src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/_create.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def _build_arguments_schema(cls, *args, **kwargs):
5757
# define Arg Group "Properties"
5858

5959
_args_schema = cls._args_schema
60-
_args_schema.additional_data = AAZObjectArg(
60+
_args_schema.additional_data = AAZFreeFormDictArg(
6161
options=["--additional-data"],
6262
arg_group="Properties",
6363
help="Additional metadata for the change required for various orchestration tools.",
@@ -139,7 +139,7 @@ def _build_arguments_schema(cls, *args, **kwargs):
139139
)
140140

141141
change_definition = cls._args_schema.change_definition
142-
change_definition.details = AAZObjectArg(
142+
change_definition.details = AAZFreeFormDictArg(
143143
options=["details"],
144144
help="Free form object containing additional details for the change definition.",
145145
required=True,
@@ -371,7 +371,7 @@ def content(self):
371371

372372
properties = _builder.get(".properties")
373373
if properties is not None:
374-
properties.set_prop("additionalData", AAZObjectType, ".additional_data")
374+
properties.set_prop("additionalData", AAZFreeFormDictType, ".additional_data")
375375
properties.set_prop("anticipatedEndTime", AAZStrType, ".anticipated_end_time", typ_kwargs={"flags": {"required": True}})
376376
properties.set_prop("anticipatedStartTime", AAZStrType, ".anticipated_start_time", typ_kwargs={"flags": {"required": True}})
377377
properties.set_prop("changeDefinition", AAZObjectType, ".change_definition", typ_kwargs={"flags": {"required": True}})
@@ -387,7 +387,7 @@ def content(self):
387387

388388
change_definition = _builder.get(".properties.changeDefinition")
389389
if change_definition is not None:
390-
change_definition.set_prop("details", AAZObjectType, ".details", typ_kwargs={"flags": {"required": True}})
390+
change_definition.set_prop("details", AAZFreeFormDictType, ".details", typ_kwargs={"flags": {"required": True}})
391391
change_definition.set_prop("kind", AAZStrType, ".kind", typ_kwargs={"flags": {"required": True}})
392392
change_definition.set_prop("name", AAZStrType, ".name", typ_kwargs={"flags": {"required": True}})
393393

@@ -510,7 +510,7 @@ def _build_schema_on_200_201(cls):
510510
)
511511

512512
properties = cls._schema_on_200_201.properties
513-
properties.additional_data = AAZObjectType(
513+
properties.additional_data = AAZFreeFormDictType(
514514
serialized_name="additionalData",
515515
)
516516
properties.anticipated_end_time = AAZStrType(
@@ -559,7 +559,7 @@ def _build_schema_on_200_201(cls):
559559
)
560560

561561
change_definition = cls._schema_on_200_201.properties.change_definition
562-
change_definition.details = AAZObjectType(
562+
change_definition.details = AAZFreeFormDictType(
563563
flags={"required": True},
564564
)
565565
change_definition.kind = AAZStrType(
@@ -754,7 +754,7 @@ def content(self):
754754

755755
properties = _builder.get(".properties")
756756
if properties is not None:
757-
properties.set_prop("additionalData", AAZObjectType, ".additional_data")
757+
properties.set_prop("additionalData", AAZFreeFormDictType, ".additional_data")
758758
properties.set_prop("anticipatedEndTime", AAZStrType, ".anticipated_end_time", typ_kwargs={"flags": {"required": True}})
759759
properties.set_prop("anticipatedStartTime", AAZStrType, ".anticipated_start_time", typ_kwargs={"flags": {"required": True}})
760760
properties.set_prop("changeDefinition", AAZObjectType, ".change_definition", typ_kwargs={"flags": {"required": True}})
@@ -770,7 +770,7 @@ def content(self):
770770

771771
change_definition = _builder.get(".properties.changeDefinition")
772772
if change_definition is not None:
773-
change_definition.set_prop("details", AAZObjectType, ".details", typ_kwargs={"flags": {"required": True}})
773+
change_definition.set_prop("details", AAZFreeFormDictType, ".details", typ_kwargs={"flags": {"required": True}})
774774
change_definition.set_prop("kind", AAZStrType, ".kind", typ_kwargs={"flags": {"required": True}})
775775
change_definition.set_prop("name", AAZStrType, ".name", typ_kwargs={"flags": {"required": True}})
776776

@@ -893,7 +893,7 @@ def _build_schema_on_200_201(cls):
893893
)
894894

895895
properties = cls._schema_on_200_201.properties
896-
properties.additional_data = AAZObjectType(
896+
properties.additional_data = AAZFreeFormDictType(
897897
serialized_name="additionalData",
898898
)
899899
properties.anticipated_end_time = AAZStrType(
@@ -942,7 +942,7 @@ def _build_schema_on_200_201(cls):
942942
)
943943

944944
change_definition = cls._schema_on_200_201.properties.change_definition
945-
change_definition.details = AAZObjectType(
945+
change_definition.details = AAZFreeFormDictType(
946946
flags={"required": True},
947947
)
948948
change_definition.kind = AAZStrType(

src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/_list.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ def _build_schema_on_200(cls):
169169
)
170170

171171
properties = cls._schema_on_200.value.Element.properties
172-
properties.additional_data = AAZObjectType(
172+
properties.additional_data = AAZFreeFormDictType(
173173
serialized_name="additionalData",
174174
)
175175
properties.anticipated_end_time = AAZStrType(
@@ -218,7 +218,7 @@ def _build_schema_on_200(cls):
218218
)
219219

220220
change_definition = cls._schema_on_200.value.Element.properties.change_definition
221-
change_definition.details = AAZObjectType(
221+
change_definition.details = AAZFreeFormDictType(
222222
flags={"required": True},
223223
)
224224
change_definition.kind = AAZStrType(
@@ -440,7 +440,7 @@ def _build_schema_on_200(cls):
440440
)
441441

442442
properties = cls._schema_on_200.value.Element.properties
443-
properties.additional_data = AAZObjectType(
443+
properties.additional_data = AAZFreeFormDictType(
444444
serialized_name="additionalData",
445445
)
446446
properties.anticipated_end_time = AAZStrType(
@@ -489,7 +489,7 @@ def _build_schema_on_200(cls):
489489
)
490490

491491
change_definition = cls._schema_on_200.value.Element.properties.change_definition
492-
change_definition.details = AAZObjectType(
492+
change_definition.details = AAZFreeFormDictType(
493493
flags={"required": True},
494494
)
495495
change_definition.kind = AAZStrType(

src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/_show.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ def _build_schema_on_200(cls):
171171
)
172172

173173
properties = cls._schema_on_200.properties
174-
properties.additional_data = AAZObjectType(
174+
properties.additional_data = AAZFreeFormDictType(
175175
serialized_name="additionalData",
176176
)
177177
properties.anticipated_end_time = AAZStrType(
@@ -220,7 +220,7 @@ def _build_schema_on_200(cls):
220220
)
221221

222222
change_definition = cls._schema_on_200.properties.change_definition
223-
change_definition.details = AAZObjectType(
223+
change_definition.details = AAZFreeFormDictType(
224224
flags={"required": True},
225225
)
226226
change_definition.kind = AAZStrType(
@@ -435,7 +435,7 @@ def _build_schema_on_200(cls):
435435
)
436436

437437
properties = cls._schema_on_200.properties
438-
properties.additional_data = AAZObjectType(
438+
properties.additional_data = AAZFreeFormDictType(
439439
serialized_name="additionalData",
440440
)
441441
properties.anticipated_end_time = AAZStrType(
@@ -484,7 +484,7 @@ def _build_schema_on_200(cls):
484484
)
485485

486486
change_definition = cls._schema_on_200.properties.change_definition
487-
change_definition.details = AAZObjectType(
487+
change_definition.details = AAZFreeFormDictType(
488488
flags={"required": True},
489489
)
490490
change_definition.kind = AAZStrType(

src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/_update.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def _build_arguments_schema(cls, *args, **kwargs):
6060
# define Arg Group "Properties"
6161

6262
_args_schema = cls._args_schema
63-
_args_schema.additional_data = AAZObjectArg(
63+
_args_schema.additional_data = AAZFreeFormDictArg(
6464
options=["--additional-data"],
6565
arg_group="Properties",
6666
help="Additional metadata for the change required for various orchestration tools.",
@@ -150,7 +150,7 @@ def _build_arguments_schema(cls, *args, **kwargs):
150150
)
151151

152152
change_definition = cls._args_schema.change_definition
153-
change_definition.details = AAZObjectArg(
153+
change_definition.details = AAZFreeFormDictArg(
154154
options=["details"],
155155
help="Free form object containing additional details for the change definition.",
156156
blank={},
@@ -717,7 +717,7 @@ def _update_instance(self, instance):
717717

718718
properties = _builder.get(".properties")
719719
if properties is not None:
720-
properties.set_prop("additionalData", AAZObjectType, ".additional_data")
720+
properties.set_prop("additionalData", AAZFreeFormDictType, ".additional_data")
721721
properties.set_prop("anticipatedEndTime", AAZStrType, ".anticipated_end_time", typ_kwargs={"flags": {"required": True}})
722722
properties.set_prop("anticipatedStartTime", AAZStrType, ".anticipated_start_time", typ_kwargs={"flags": {"required": True}})
723723
properties.set_prop("changeDefinition", AAZObjectType, ".change_definition", typ_kwargs={"flags": {"required": True}})
@@ -733,7 +733,7 @@ def _update_instance(self, instance):
733733

734734
change_definition = _builder.get(".properties.changeDefinition")
735735
if change_definition is not None:
736-
change_definition.set_prop("details", AAZObjectType, ".details", typ_kwargs={"flags": {"required": True}})
736+
change_definition.set_prop("details", AAZFreeFormDictType, ".details", typ_kwargs={"flags": {"required": True}})
737737
change_definition.set_prop("kind", AAZStrType, ".kind", typ_kwargs={"flags": {"required": True}})
738738
change_definition.set_prop("name", AAZStrType, ".name", typ_kwargs={"flags": {"required": True}})
739739

@@ -865,7 +865,7 @@ def _build_schema_change_record_read(cls, _schema):
865865
)
866866

867867
properties = _schema_change_record_read.properties
868-
properties.additional_data = AAZObjectType(
868+
properties.additional_data = AAZFreeFormDictType(
869869
serialized_name="additionalData",
870870
)
871871
properties.anticipated_end_time = AAZStrType(
@@ -914,7 +914,7 @@ def _build_schema_change_record_read(cls, _schema):
914914
)
915915

916916
change_definition = _schema_change_record_read.properties.change_definition
917-
change_definition.details = AAZObjectType(
917+
change_definition.details = AAZFreeFormDictType(
918918
flags={"required": True},
919919
)
920920
change_definition.kind = AAZStrType(

src/azure-changesafety/azext_changesafety/custom.py

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,28 @@ def _inject_change_definition_into_content(content, ctx):
144144
return content
145145

146146

147+
def _inject_additional_data_into_content(content, ctx):
148+
"""Inject the parsed additionalData into the serialized request content.
149+
150+
The AAZ content builder cannot serialize AAZFreeFormDictType correctly
151+
(it produces {}), so we capture the raw value in pre_operations and
152+
inject it here — the same pattern used for changeDefinition.
153+
"""
154+
additional_data_value = getattr(ctx.vars, "additional_data", None)
155+
if additional_data_value is None:
156+
return content
157+
158+
additional_data = additional_data_value.to_serialized_data()
159+
if additional_data is None:
160+
return content
161+
162+
if content is None:
163+
content = {}
164+
properties = content.setdefault("properties", {})
165+
properties["additionalData"] = additional_data
166+
return content
167+
168+
147169
def _preserve_change_definition_in_content(content, ctx):
148170
"""Preserve the original changeDefinition from GET response in the update request.
149171
@@ -356,6 +378,19 @@ def pre_operations(self):
356378
self._ensure_schedule_defaults()
357379
_apply_stage_map_shortcut(self.ctx)
358380

381+
# Capture additional_data for injection into request content.
382+
# The AAZ builder cannot serialize AAZFreeFormDictType, so we
383+
# store the raw value and inject it via the content property.
384+
additional_data_arg = getattr(self.ctx.args, "additional_data", None)
385+
if has_value(additional_data_arg):
386+
additional_data = additional_data_arg.to_serialized_data()
387+
if additional_data is not None:
388+
self.ctx.set_var(
389+
'additional_data',
390+
additional_data,
391+
schema_builder=_build_any_type,
392+
)
393+
359394
change_definition_arg = getattr(self.ctx.args, "change_definition", None)
360395
change_definition_value = None
361396
self._raw_targets = [t for t in (self._raw_targets or []) if t and str(t) != 'Undefined']
@@ -490,21 +525,25 @@ def _output(self, *args, **kwargs):
490525

491526
class ChangeRecordsCreateOrUpdateAtSubscriptionLevel(
492527
_ChangeRecordCreate.ChangeRecordsCreateOrUpdateAtSubscriptionLevel):
493-
"""Override PUT at subscription level to inject custom changeDefinition."""
528+
"""Override PUT at subscription level to inject custom payloads."""
494529

495530
@property
496531
def content(self):
497532
content = super().content
498-
return _inject_change_definition_into_content(content, self.ctx)
533+
content = _inject_change_definition_into_content(content, self.ctx)
534+
content = _inject_additional_data_into_content(content, self.ctx)
535+
return content
499536

500537
class ChangeRecordsCreateOrUpdate(
501538
_ChangeRecordCreate.ChangeRecordsCreateOrUpdate):
502-
"""Override PUT at resource group level to inject custom changeDefinition."""
539+
"""Override PUT at resource group level to inject custom payloads."""
503540

504541
@property
505542
def content(self):
506543
content = super().content
507-
return _inject_change_definition_into_content(content, self.ctx)
544+
content = _inject_change_definition_into_content(content, self.ctx)
545+
content = _inject_additional_data_into_content(content, self.ctx)
546+
return content
508547

509548

510549
class ChangeRecordUpdate(_ChangeRecordUpdate):
@@ -539,6 +578,17 @@ def pre_operations(self):
539578
super().pre_operations()
540579
_apply_stage_map_shortcut(self.ctx)
541580

581+
# Capture additional_data for injection (same pattern as Create)
582+
additional_data_arg = getattr(self.ctx.args, "additional_data", None)
583+
if has_value(additional_data_arg):
584+
additional_data = additional_data_arg.to_serialized_data()
585+
if additional_data is not None:
586+
self.ctx.set_var(
587+
'additional_data',
588+
additional_data,
589+
schema_builder=_build_any_type,
590+
)
591+
542592
class ChangeRecordsGetAtSubscriptionLevel(
543593
_ChangeRecordUpdate.ChangeRecordsGetAtSubscriptionLevel):
544594
"""Override GET at subscription level to capture original changeDefinition."""
@@ -572,23 +622,25 @@ def on_200(self, session):
572622

573623
class ChangeRecordsCreateOrUpdateAtSubscriptionLevel(
574624
_ChangeRecordUpdate.ChangeRecordsCreateOrUpdateAtSubscriptionLevel):
575-
"""Override PUT at subscription level to preserve original changeDefinition."""
625+
"""Override PUT at subscription level to preserve changeDefinition and inject additionalData."""
576626

577627
@property
578628
def content(self):
579629
content = super().content
580-
# Preserve original changeDefinition - it cannot be updated
581-
return _preserve_change_definition_in_content(content, self.ctx)
630+
content = _preserve_change_definition_in_content(content, self.ctx)
631+
content = _inject_additional_data_into_content(content, self.ctx)
632+
return content
582633

583634
class ChangeRecordsCreateOrUpdate(
584635
_ChangeRecordUpdate.ChangeRecordsCreateOrUpdate):
585-
"""Override PUT at resource group level to preserve original changeDefinition."""
636+
"""Override PUT at resource group level to preserve changeDefinition and inject additionalData."""
586637

587638
@property
588639
def content(self):
589640
content = super().content
590-
# Preserve original changeDefinition - it cannot be updated
591-
return _preserve_change_definition_in_content(content, self.ctx)
641+
content = _preserve_change_definition_in_content(content, self.ctx)
642+
content = _inject_additional_data_into_content(content, self.ctx)
643+
return content
592644

593645

594646
class ChangeRecordShow(_ChangeRecordShow):

0 commit comments

Comments
 (0)