Skip to content

Commit e9e22cb

Browse files
msyycCopilot
andauthored
[changelog tool] Report added operation group classes correctly in changelog (#48110)
* Report added operation group classes correctly in changelog Newly added operation group classes (e.g. PrivateLinkResourcesOperations) were mislabeled as 'Added model'. Detect operation group classes by name ending with 'operations' in an operations module and emit 'Added operation group' instead, removing the need for manual changelog optimization. Fixes #48107 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: cc14cf8a-5eef-4d13-9581-59d1e5491585 * Refresh apimanagement expected changelog for operation group classes The four added operation group classes are now reported as 'Added operation group' instead of 'Added model'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: cc14cf8a-5eef-4d13-9581-59d1e5491585 --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 362d68d commit e9e22cb

4 files changed

Lines changed: 84 additions & 4 deletions

File tree

scripts/breaking_changes_checker/breaking_changes_tracker.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,22 @@ def is_client(self, module_name: Any, class_name: Any) -> bool:
149149
return True
150150
return False
151151

152+
def is_operation_group(self, module_name: Any, class_name: Any) -> bool:
153+
"""Determine whether a class should be treated as an operation group.
154+
155+
Operation group classes follow the codegen convention of a name ending
156+
with ``Operations`` (case-insensitive, which also covers the default
157+
``Operations`` class) and being defined in an operations module (module
158+
namespace ending with ``operations``). This mirrors the existing
159+
``attr_type.lower().endswith("operations")`` heuristic used elsewhere to
160+
recognize operation group properties on clients.
161+
"""
162+
if isinstance(class_name, jsondiff.Symbol) or not isinstance(class_name, str):
163+
return False
164+
if isinstance(module_name, jsondiff.Symbol) or not isinstance(module_name, str):
165+
return False
166+
return class_name.lower().endswith("operations") and module_name.endswith("operations")
167+
152168
def run_post_processing(self) -> None:
153169
# Remove duplicate reporting of changes that apply to both sync and async package components
154170
self.run_async_cleanup(self.breaking_changes)

scripts/breaking_changes_checker/changelog_tracker.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class ChangeType(str, Enum):
2222
ADDED_ENUM_MEMBER = "AddedEnumMember"
2323
ADDED_FUNCTION_PARAMETER = "AddedFunctionParameter"
2424
ADDED_OPERATION_GROUP = "AddedOperationGroup"
25+
ADDED_OPERATION_GROUP_CLASS = "AddedOperationGroupClass"
2526

2627
class ChangelogTracker(BreakingChangesTracker):
2728
ADDED_CLIENT_MSG = \
@@ -46,6 +47,8 @@ class ChangelogTracker(BreakingChangesTracker):
4647
"Enum `{}` added member `{}`"
4748
ADDED_OPERATION_GROUP_MSG = \
4849
"Client `{}` added operation group `{}`"
50+
ADDED_OPERATION_GROUP_CLASS_MSG = \
51+
"Added operation group `{}`"
4952

5053

5154
def __init__(self, stable: Dict, current: Dict, package_name: str, **kwargs: Any) -> None:
@@ -87,6 +90,14 @@ def run_non_breaking_class_level_diff_checks(self, module: Dict) -> None:
8790
self.module_name, class_name
8891
)
8992
self.features_added.append(fa)
93+
elif self.is_operation_group(self.module_name, self.class_name):
94+
# This is a new operation group class
95+
fa = (
96+
self.ADDED_OPERATION_GROUP_CLASS_MSG,
97+
ChangeType.ADDED_OPERATION_GROUP_CLASS,
98+
self.module_name, class_name
99+
)
100+
self.features_added.append(fa)
90101
else:
91102
# This is a new class
92103
fa = (

scripts/breaking_changes_checker/tests/data/expected_apimanagement_5_6b1_changelog.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -672,10 +672,10 @@
672672
- Model `WorkspaceTagOperations` added parameter `match_condition` in method `delete`
673673
- Model `WorkspaceTagOperations` added parameter `etag` in method `update`
674674
- Model `WorkspaceTagOperations` added parameter `match_condition` in method `update`
675-
- Added model `ApiGatewayHostnameBindingOperations`
676-
- Added model `ApiToolOperations`
677-
- Added model `ClientApplicationOperations`
678-
- Added model `ClientApplicationProductLinkOperations`
675+
- Added operation group `ApiGatewayHostnameBindingOperations`
676+
- Added operation group `ApiToolOperations`
677+
- Added operation group `ClientApplicationOperations`
678+
- Added operation group `ClientApplicationProductLinkOperations`
679679

680680
### Breaking Changes
681681

scripts/breaking_changes_checker/tests/test_changelog.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,59 @@ def test_new_class_property_added_init():
447447
assert args == ['azure.ai.contentsafety', 'AnalyzeTextResult', 'new_class_att']
448448

449449

450+
def test_added_operation_group_class_not_labeled_as_model():
451+
# A newly added operation group class must be reported as "Added operation group `X`",
452+
# not "Added model `X`". It appears in both the sync `...operations` module and the async
453+
# `...aio.operations` module, but the async cleanup should collapse it into a single entry.
454+
def _op_group_module():
455+
return {
456+
"class_nodes": {
457+
"ExistingOperations": {
458+
"type": None,
459+
"methods": {},
460+
"properties": {}
461+
},
462+
"PrivateLinkResourcesOperations": {
463+
"type": None,
464+
"methods": {},
465+
"properties": {}
466+
}
467+
}
468+
}
469+
470+
def _stable_op_group_module():
471+
return {
472+
"class_nodes": {
473+
"ExistingOperations": {
474+
"type": None,
475+
"methods": {},
476+
"properties": {}
477+
}
478+
}
479+
}
480+
481+
current = {
482+
"azure.mgmt.attestation.operations": _op_group_module(),
483+
"azure.mgmt.attestation.aio.operations": _op_group_module(),
484+
}
485+
stable = {
486+
"azure.mgmt.attestation.operations": _stable_op_group_module(),
487+
"azure.mgmt.attestation.aio.operations": _stable_op_group_module(),
488+
}
489+
490+
bc = ChangelogTracker(stable, current, "azure-mgmt-attestation")
491+
bc.run_checks()
492+
493+
op_group_entries = [fa for fa in bc.features_added if fa[0] == ChangelogTracker.ADDED_OPERATION_GROUP_CLASS_MSG]
494+
added_model_entries = [fa for fa in bc.features_added if fa[0] == ChangelogTracker.ADDED_CLASS_MSG]
495+
496+
# Exactly one operation group entry (sync/async duplicate collapsed) and no "Added model".
497+
assert len(op_group_entries) == 1
498+
assert not added_model_entries
499+
_, _, _, class_name = op_group_entries[0]
500+
assert class_name == "PrivateLinkResourcesOperations"
501+
502+
450503
def test_new_class_property_added_init_only():
451504
# Testing if we get a report on a new class property added only in the init
452505
current = {

0 commit comments

Comments
 (0)