azdev 0.1.81__tar.gz → 0.1.83__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. {azdev-0.1.81 → azdev-0.1.83}/HISTORY.rst +8 -0
  2. {azdev-0.1.81/azdev.egg-info → azdev-0.1.83}/PKG-INFO +9 -1
  3. {azdev-0.1.81 → azdev-0.1.83}/azdev/__init__.py +1 -1
  4. {azdev-0.1.81 → azdev-0.1.83}/azdev/commands.py +3 -0
  5. {azdev-0.1.81 → azdev-0.1.83}/azdev/help.py +11 -0
  6. azdev-0.1.83/azdev/operations/breaking_change/__init__.py +316 -0
  7. azdev-0.1.83/azdev/operations/breaking_change/markdown_template.jinja2 +28 -0
  8. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/command_change/__init__.py +3 -21
  9. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/statistics/__init__.py +6 -4
  10. {azdev-0.1.81 → azdev-0.1.83}/azdev/params.py +14 -0
  11. {azdev-0.1.81 → azdev-0.1.83}/azdev/utilities/__init__.py +3 -1
  12. {azdev-0.1.81 → azdev-0.1.83}/azdev/utilities/path.py +27 -1
  13. {azdev-0.1.81 → azdev-0.1.83/azdev.egg-info}/PKG-INFO +9 -1
  14. {azdev-0.1.81 → azdev-0.1.83}/azdev.egg-info/SOURCES.txt +2 -0
  15. {azdev-0.1.81 → azdev-0.1.83}/setup.py +2 -0
  16. {azdev-0.1.81 → azdev-0.1.83}/LICENSE +0 -0
  17. {azdev-0.1.81 → azdev-0.1.83}/MANIFEST.in +0 -0
  18. {azdev-0.1.81 → azdev-0.1.83}/README.md +0 -0
  19. {azdev-0.1.81 → azdev-0.1.83}/README.rst +0 -0
  20. {azdev-0.1.81 → azdev-0.1.83}/azdev/__main__.py +0 -0
  21. {azdev-0.1.81 → azdev-0.1.83}/azdev/completer.py +0 -0
  22. {azdev-0.1.81 → azdev-0.1.83}/azdev/config/__init__.py +0 -0
  23. {azdev-0.1.81 → azdev-0.1.83}/azdev/config/cli.flake8 +0 -0
  24. {azdev-0.1.81 → azdev-0.1.83}/azdev/config/cli_pylintrc +0 -0
  25. {azdev-0.1.81 → azdev-0.1.83}/azdev/config/ext.flake8 +0 -0
  26. {azdev-0.1.81 → azdev-0.1.83}/azdev/config/ext_pylintrc +0 -0
  27. {azdev-0.1.81 → azdev-0.1.83}/azdev/mod_templates/HISTORY.rst +0 -0
  28. {azdev-0.1.81 → azdev-0.1.83}/azdev/mod_templates/README.rst +0 -0
  29. {azdev-0.1.81 → azdev-0.1.83}/azdev/mod_templates/_client_factory.py +0 -0
  30. {azdev-0.1.81 → azdev-0.1.83}/azdev/mod_templates/_help.py +0 -0
  31. {azdev-0.1.81 → azdev-0.1.83}/azdev/mod_templates/_params.py +0 -0
  32. {azdev-0.1.81 → azdev-0.1.83}/azdev/mod_templates/_validators.py +0 -0
  33. {azdev-0.1.81 → azdev-0.1.83}/azdev/mod_templates/azext_metadata.json +0 -0
  34. {azdev-0.1.81 → azdev-0.1.83}/azdev/mod_templates/blank__init__.py +0 -0
  35. {azdev-0.1.81 → azdev-0.1.83}/azdev/mod_templates/commands.py +0 -0
  36. {azdev-0.1.81 → azdev-0.1.83}/azdev/mod_templates/custom.py +0 -0
  37. {azdev-0.1.81 → azdev-0.1.83}/azdev/mod_templates/module__init__.py +0 -0
  38. {azdev-0.1.81 → azdev-0.1.83}/azdev/mod_templates/pkg_declare__init__.py +0 -0
  39. {azdev-0.1.81 → azdev-0.1.83}/azdev/mod_templates/setup.cfg +0 -0
  40. {azdev-0.1.81 → azdev-0.1.83}/azdev/mod_templates/setup.py +0 -0
  41. {azdev-0.1.81 → azdev-0.1.83}/azdev/mod_templates/test_service_scenario.py +0 -0
  42. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/__init__.py +0 -0
  43. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/cmdcov/__init__.py +0 -0
  44. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/cmdcov/_macros.j2 +0 -0
  45. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/cmdcov/cmdcov.py +0 -0
  46. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/cmdcov/component.css +0 -0
  47. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/cmdcov/component.js +0 -0
  48. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/cmdcov/favicon.ico +0 -0
  49. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/cmdcov/index.j2 +0 -0
  50. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/cmdcov/index2.j2 +0 -0
  51. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/cmdcov/module.j2 +0 -0
  52. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/code_gen.py +0 -0
  53. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/command_change/custom.py +0 -0
  54. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/command_change/util.py +0 -0
  55. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/constant.py +0 -0
  56. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/extensions/__init__.py +0 -0
  57. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/extensions/util.py +0 -0
  58. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/extensions/version_upgrade.py +0 -0
  59. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/help/__init__.py +0 -0
  60. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/help/refdoc/__init__.py +0 -0
  61. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/help/refdoc/conf.py +0 -0
  62. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/legal.py +0 -0
  63. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/linter/__init__.py +0 -0
  64. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/linter/linter.py +0 -0
  65. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/linter/pylint_checkers/__init__.py +0 -0
  66. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/linter/pylint_checkers/show_command.py +0 -0
  67. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/linter/rule_decorators.py +0 -0
  68. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/linter/rules/__init__.py +0 -0
  69. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/linter/rules/ci_exclusions.yml +0 -0
  70. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/linter/rules/command_coverage_rules.py +0 -0
  71. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/linter/rules/command_group_rules.py +0 -0
  72. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/linter/rules/command_rules.py +0 -0
  73. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/linter/rules/help_rules.py +0 -0
  74. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/linter/rules/linter_exclusions.yml +0 -0
  75. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/linter/rules/parameter_rules.py +0 -0
  76. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/linter/util.py +0 -0
  77. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/performance.py +0 -0
  78. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/pypi.py +0 -0
  79. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/python_sdk.py +0 -0
  80. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/regex.py +0 -0
  81. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/resource.py +0 -0
  82. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/secret.py +0 -0
  83. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/setup.py +0 -0
  84. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/statistics/util.py +0 -0
  85. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/style.py +0 -0
  86. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/testtool/__init__.py +0 -0
  87. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/testtool/incremental_strategy.py +0 -0
  88. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/testtool/profile_context.py +0 -0
  89. {azdev-0.1.81 → azdev-0.1.83}/azdev/operations/testtool/pytest_runner.py +0 -0
  90. {azdev-0.1.81 → azdev-0.1.83}/azdev/transformers.py +0 -0
  91. {azdev-0.1.81 → azdev-0.1.83}/azdev/utilities/command.py +0 -0
  92. {azdev-0.1.81 → azdev-0.1.83}/azdev/utilities/config.py +0 -0
  93. {azdev-0.1.81 → azdev-0.1.83}/azdev/utilities/const.py +0 -0
  94. {azdev-0.1.81 → azdev-0.1.83}/azdev/utilities/display.py +0 -0
  95. {azdev-0.1.81 → azdev-0.1.83}/azdev/utilities/git_util.py +0 -0
  96. {azdev-0.1.81 → azdev-0.1.83}/azdev/utilities/pypi.py +0 -0
  97. {azdev-0.1.81 → azdev-0.1.83}/azdev/utilities/testing.py +0 -0
  98. {azdev-0.1.81 → azdev-0.1.83}/azdev/utilities/tools.py +0 -0
  99. {azdev-0.1.81 → azdev-0.1.83}/azdev.egg-info/dependency_links.txt +0 -0
  100. {azdev-0.1.81 → azdev-0.1.83}/azdev.egg-info/entry_points.txt +0 -0
  101. {azdev-0.1.81 → azdev-0.1.83}/azdev.egg-info/requires.txt +0 -0
  102. {azdev-0.1.81 → azdev-0.1.83}/azdev.egg-info/top_level.txt +0 -0
  103. {azdev-0.1.81 → azdev-0.1.83}/pyproject.toml +0 -0
  104. {azdev-0.1.81 → azdev-0.1.83}/setup.cfg +0 -0
@@ -2,6 +2,14 @@
2
2
 
3
3
  Release History
4
4
  ===============
5
+ 0.1.83
6
+ ++++++
7
+ * `azdev generate-breaking-change-report`: Fix `azdev.operations.breaking_change` not included in `setup.py`.
8
+
9
+ 0.1.82
10
+ ++++++
11
+ * `azdev generate-breaking-change-report`: New command to collect upcoming breaking changes from codebase.
12
+
5
13
  0.1.81
6
14
  ++++++
7
15
  * `azdev scan/mask`: Add `--confidence-level` to support secret pattern levels
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: azdev
3
- Version: 0.1.81
3
+ Version: 0.1.83
4
4
  Summary: Microsoft Azure CLI Developer Tools
5
5
  Home-page: https://github.com/Azure/azure-cli-dev-tools
6
6
  Author: Microsoft Corporation
@@ -148,6 +148,14 @@ License
148
148
 
149
149
  Release History
150
150
  ===============
151
+ 0.1.83
152
+ ++++++
153
+ * `azdev generate-breaking-change-report`: Fix `azdev.operations.breaking_change` not included in `setup.py`.
154
+
155
+ 0.1.82
156
+ ++++++
157
+ * `azdev generate-breaking-change-report`: New command to collect upcoming breaking changes from codebase.
158
+
151
159
  0.1.81
152
160
  ++++++
153
161
  * `azdev scan/mask`: Add `--confidence-level` to support secret pattern levels
@@ -4,4 +4,4 @@
4
4
  # license information.
5
5
  # -----------------------------------------------------------------------------
6
6
 
7
- __VERSION__ = '0.1.81'
7
+ __VERSION__ = '0.1.83'
@@ -84,3 +84,6 @@ def load_command_table(self, _):
84
84
 
85
85
  with CommandGroup(self, 'extension', operation_group('help')) as g:
86
86
  g.command('generate-docs', 'generate_extension_ref_docs')
87
+
88
+ with CommandGroup(self, '', operation_group('breaking_change')) as g:
89
+ g.command('generate-breaking-change-report', 'collect_upcoming_breaking_changes')
@@ -377,3 +377,14 @@ helps['cmdcov'] = """
377
377
  - name: Check CLI modules command test coverage in argument level.
378
378
  text: azdev cmdcov CLI --level argument
379
379
  """
380
+
381
+ helps['generate-breaking-change-report'] = """
382
+ short-summary: Collect pre-announced breaking changes items and generate the report.
383
+ examples:
384
+ - name: Collect all pre-announced breaking changes, including any that did not specify a target version and group them by target version.
385
+ text: azdev generate-breaking-change-report CLI --group-by-version --target-version None
386
+ - name: Collect all pre-announced breaking changes target before next breaking change window, and display them in markdown.
387
+ text: azdev generate-breaking-change-report CLI --output-format markdown
388
+ - name: Collect all pre-announced breaking changes in vm, including those failed to specify a target version, and display them in json
389
+ text: azdev generate-breaking-change-report vm --target-version None
390
+ """
@@ -0,0 +1,316 @@
1
+ # -----------------------------------------------------------------------------
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # Licensed under the MIT License. See License.txt in the project root for
4
+ # license information.
5
+ # -----------------------------------------------------------------------------
6
+ import re
7
+ import time
8
+ from collections import defaultdict
9
+ from importlib import import_module
10
+
11
+ import packaging.version
12
+ from azure.cli.core.breaking_change import MergedStatusTag, UpcomingBreakingChangeTag, TargetVersion
13
+ from knack.deprecation import Deprecated
14
+ from knack.log import get_logger
15
+
16
+ from azdev.operations.statistics import _create_invoker_and_load_cmds # pylint: disable=protected-access
17
+ from azdev.utilities import require_azure_cli, display, heading, output, calc_selected_mod_names
18
+
19
+ # pylint: disable=no-else-return
20
+
21
+ logger = get_logger(__name__)
22
+
23
+
24
+ class BreakingChangeItem:
25
+ def __init__(self, module, command, detail, target_version):
26
+ self.module = module
27
+ self.command = command
28
+ self.detail = detail
29
+ self.target_version = target_version
30
+
31
+
32
+ def _load_commands():
33
+ start = time.time()
34
+ display('Initializing with loading command table...')
35
+ from azure.cli.core import get_default_cli # pylint: disable=import-error
36
+ az_cli = get_default_cli()
37
+
38
+ # load commands, args, and help
39
+ # The arguments must be loaded before the `EVENT_INVOKER_POST_CMD_TBL_CREATE` event.
40
+ # This is because we generate the `deprecate_info` and `upcoming_breaking_change` tags from pre-announcement data
41
+ # during the event.
42
+ # If the arguments are not loaded beforehand, this information will not be included.
43
+ _create_invoker_and_load_cmds(az_cli, load_arguments=True)
44
+
45
+ stop = time.time()
46
+ logger.info('Commands loaded in %i sec', stop - start)
47
+ display('Commands loaded in {} sec'.format(stop - start))
48
+ command_loader = az_cli.invocation.commands_loader
49
+
50
+ if not command_loader.command_table:
51
+ logger.warning('No commands selected to check.')
52
+ return command_loader
53
+
54
+
55
+ def _handle_custom_breaking_changes(module, command):
56
+ """
57
+ Collect Custom Pre-Announcement defined in `_breaking_change.py`
58
+ :param module: module name
59
+ :param command: command name
60
+ :return: A generated returns Custom Pre-Announcements defined in `_breaking_change.py`
61
+ """
62
+ from azure.cli.core.breaking_change import upcoming_breaking_changes
63
+ yield from _handle_custom_breaking_change(module, command, upcoming_breaking_changes.get(command))
64
+ for key in upcoming_breaking_changes:
65
+ if key.startswith(command + '.'):
66
+ yield from _handle_custom_breaking_change(module, command, upcoming_breaking_changes[key])
67
+
68
+
69
+ def _handle_custom_breaking_change(module, command, breaking_change):
70
+ """
71
+ Handle a BreakingChange item defined in `_breaking_change.py`. We need this method because the item stored could
72
+ be a list or object
73
+ """
74
+ from azure.cli.core.breaking_change import BreakingChange
75
+ if isinstance(breaking_change, str):
76
+ yield BreakingChangeItem(module, command, breaking_change, None)
77
+ elif isinstance(breaking_change, BreakingChange):
78
+ yield BreakingChangeItem(module, command, breaking_change.message, breaking_change.target_version.version())
79
+ elif isinstance(breaking_change, list):
80
+ for bc in breaking_change:
81
+ yield from _handle_custom_breaking_change(module, command, bc)
82
+
83
+
84
+ def _handle_status_tag(module, command, status_tag):
85
+ if isinstance(status_tag, MergedStatusTag):
86
+ for tag in status_tag.tags:
87
+ yield from _handle_status_tag(module, command, tag)
88
+ else:
89
+ detail = status_tag._get_message(status_tag) # pylint: disable=protected-access
90
+ version = None
91
+ if isinstance(status_tag, Deprecated):
92
+ version = status_tag.expiration
93
+ elif isinstance(status_tag, UpcomingBreakingChangeTag):
94
+ if isinstance(status_tag.target_version, TargetVersion):
95
+ version = status_tag.target_version.version()
96
+ elif isinstance(status_tag.target_version, str):
97
+ version = status_tag.target_version
98
+ if version is None:
99
+ version_match = re.search(r'\d+\.\d+\.\d+', detail)
100
+ if version_match:
101
+ version = version_match.group(0)
102
+ yield BreakingChangeItem(module, command, detail, version)
103
+
104
+
105
+ def _handle_command_deprecation(module, command, deprecate_info):
106
+ yield from _handle_status_tag(module, command, deprecate_info)
107
+
108
+
109
+ def _calc_target_of_arg_deprecation(arg_name, arg_settings):
110
+ option_str_list = []
111
+ depr = arg_settings.get('deprecate_info')
112
+ for option in arg_settings.get('option_list', []):
113
+ if isinstance(option, str):
114
+ option_str_list.append(option)
115
+ elif isinstance(option, Deprecated):
116
+ option_str_list.append(option.target)
117
+ if option_str_list:
118
+ return '/'.join(option_str_list)
119
+ elif hasattr(depr, 'target'):
120
+ return depr.target
121
+ else:
122
+ return arg_name
123
+
124
+
125
+ def _handle_arg_deprecation(module, command, target, deprecation_info):
126
+ deprecation_info.target = target
127
+ yield from _handle_status_tag(module, command, deprecation_info)
128
+
129
+
130
+ def _handle_options_deprecation(module, command, options):
131
+ deprecate_option_map = defaultdict(lambda: [])
132
+ for option in options:
133
+ if isinstance(option, Deprecated):
134
+ key = f'{option.redirect}|{option.expiration}|{option.hide}'
135
+ deprecate_option_map[key].append(option)
136
+ for _, depr_list in deprecate_option_map.items():
137
+ target = '/'.join([depr.target for depr in depr_list])
138
+ depr = depr_list[0]
139
+ depr.target = target
140
+ yield from _handle_status_tag(module, command, depr)
141
+
142
+
143
+ def _handle_command_breaking_changes(module, command, command_info, source):
144
+ if source == "deprecate_info":
145
+ if hasattr(command_info, "deprecate_info") and command_info.deprecate_info:
146
+ yield from _handle_command_deprecation(module, command, command_info.deprecate_info)
147
+
148
+ for argument_name, argument in command_info.arguments.items():
149
+ arg_settings = argument.type.settings
150
+ depr = arg_settings.get('deprecate_info')
151
+ if depr:
152
+ bc_target = _calc_target_of_arg_deprecation(argument_name, arg_settings)
153
+ yield from _handle_arg_deprecation(module, command, bc_target, depr)
154
+ yield from _handle_options_deprecation(module, command, arg_settings.get('options_list', []))
155
+ if source == "pre_announce":
156
+ yield from _handle_custom_breaking_changes(module, command)
157
+
158
+
159
+ def _handle_command_group_deprecation(module, command, deprecate_info):
160
+ yield from _handle_status_tag(module, command, deprecate_info)
161
+
162
+
163
+ def _handle_command_group_breaking_changes(module, command_group_name, command_group_info, source):
164
+ if source == "deprecate_info":
165
+ if hasattr(command_group_info, 'group_kwargs') and command_group_info.group_kwargs.get('deprecate_info'):
166
+ yield from _handle_command_group_deprecation(module, command_group_name,
167
+ command_group_info.group_kwargs.get('deprecate_info'))
168
+
169
+ if source == "pre_announce":
170
+ yield from _handle_custom_breaking_changes(module, command_group_name)
171
+
172
+
173
+ def _get_mod_ext_name(loader):
174
+ # There could be different name with module name in extension.
175
+ # For example, module name of `application-insights` is azext_applicationinsights
176
+ try:
177
+ module_source = next(iter(loader.command_table.values())).command_source
178
+ if isinstance(module_source, str):
179
+ return module_source
180
+ else:
181
+ return module_source.extension_name
182
+ except StopIteration:
183
+ logger.warning('There is no command in Loader(%s)', loader)
184
+ mod_path = loader.__class__.__module__
185
+ mod_name = mod_path.rsplit('.', maxsplit=1)[-1]
186
+ mod_name = mod_name.replace('azext_', '', 1)
187
+ return mod_name
188
+
189
+
190
+ def _iter_and_prepare_module_loader(command_loader, selected_mod_names):
191
+ for loader in command_loader.loaders:
192
+ module_path = loader.__class__.__module__
193
+ module_name = module_path.rsplit('.', maxsplit=1)[-1]
194
+ if module_name and module_name not in selected_mod_names:
195
+ continue
196
+
197
+ _breaking_change_module = f'{module_path}._breaking_change'
198
+ try:
199
+ import_module(_breaking_change_module)
200
+ except ImportError:
201
+ pass
202
+ loader.skip_applicability = True
203
+
204
+ yield module_name, loader
205
+
206
+
207
+ def _handle_module(module, loader, source):
208
+ start = time.time()
209
+
210
+ for command, command_info in loader.command_table.items():
211
+ yield from _handle_command_breaking_changes(module, command, command_info, source)
212
+
213
+ for command_group_name, command_group in loader.command_group_table.items():
214
+ yield from _handle_command_group_breaking_changes(module, command_group_name, command_group, source)
215
+
216
+ stop = time.time()
217
+ logger.info('Module %s finished in %i sec', module, stop - start)
218
+ display('Module {} finished loaded in {} sec'.format(module, stop - start))
219
+
220
+
221
+ def _handle_core(source):
222
+ start = time.time()
223
+ if source == "pre_announce":
224
+ core_module = 'azure.cli.core'
225
+ _breaking_change_module = f'{core_module}._breaking_change'
226
+ try:
227
+ import_module(_breaking_change_module)
228
+ except ImportError:
229
+ pass
230
+
231
+ yield from _handle_custom_breaking_changes('core', 'core')
232
+
233
+ stop = time.time()
234
+ logger.info('Core finished in %i sec', stop - start)
235
+ display('Core finished loaded in {} sec'.format(stop - start))
236
+
237
+
238
+ def _handle_upcoming_breaking_changes(selected_mod_names, source):
239
+ command_loader = _load_commands()
240
+
241
+ if 'core' in selected_mod_names or 'azure-cli-core' in selected_mod_names:
242
+ yield from _handle_core(source)
243
+
244
+ for module, loader in _iter_and_prepare_module_loader(command_loader, selected_mod_names):
245
+ yield from _handle_module(module, loader, source)
246
+
247
+
248
+ def _filter_breaking_changes(iterator, max_version=None):
249
+ if not max_version:
250
+ yield from iterator
251
+ return
252
+ try:
253
+ parsed_max_version = packaging.version.parse(max_version)
254
+ except packaging.version.InvalidVersion:
255
+ logger.warning('Invalid target version: %s; '
256
+ 'Will present all upcoming breaking changes as alternative.', max_version)
257
+ yield from iterator
258
+ return
259
+ for item in iterator:
260
+ if item.target_version:
261
+ try:
262
+ target_version = packaging.version.parse(item.target_version)
263
+ if target_version <= parsed_max_version:
264
+ yield item
265
+ except packaging.version.InvalidVersion:
266
+ logger.warning('Invalid version from `%s`: %s', item.command, item.target_version)
267
+
268
+
269
+ # pylint: disable=unnecessary-lambda-assignment
270
+ def _group_breaking_change_items(iterator, group_by_version=False):
271
+ if group_by_version:
272
+ upcoming_breaking_changes = defaultdict( # module to command
273
+ lambda: defaultdict( # command to version
274
+ lambda: defaultdict( # version to list of breaking changes
275
+ lambda: [])))
276
+ else:
277
+ upcoming_breaking_changes = defaultdict( # module to command
278
+ lambda: defaultdict( # command to list of breaking changes
279
+ lambda: []))
280
+ for item in iterator:
281
+ version = item.target_version if item.target_version else 'Unspecific'
282
+ if group_by_version:
283
+ upcoming_breaking_changes[item.module][item.command][version].append(item.detail)
284
+ else:
285
+ upcoming_breaking_changes[item.module][item.command].append(item.detail)
286
+ return upcoming_breaking_changes
287
+
288
+
289
+ def collect_upcoming_breaking_changes(modules=None, target_version='NextWindow', source=None, group_by_version=None,
290
+ output_format='structure'):
291
+ if target_version == 'NextWindow':
292
+ from azure.cli.core.breaking_change import NEXT_BREAKING_CHANGE_RELEASE
293
+ target_version = NEXT_BREAKING_CHANGE_RELEASE
294
+ elif target_version.lower() == 'none':
295
+ target_version = None
296
+
297
+ require_azure_cli()
298
+
299
+ selected_mod_names = calc_selected_mod_names(modules)
300
+
301
+ if selected_mod_names:
302
+ display('Modules selected: {}\n'.format(', '.join(selected_mod_names)))
303
+
304
+ heading('Collecting Breaking Change Pre-announcement')
305
+ breaking_changes = _handle_upcoming_breaking_changes(selected_mod_names, source)
306
+ breaking_changes = _filter_breaking_changes(breaking_changes, target_version)
307
+ breaking_changes = _group_breaking_change_items(breaking_changes, group_by_version)
308
+ if output_format == 'structure':
309
+ return breaking_changes
310
+ elif output_format == 'markdown':
311
+ from jinja2 import Environment, PackageLoader
312
+ env = Environment(loader=PackageLoader('azdev', 'operations/breaking_change'),
313
+ trim_blocks=True)
314
+ template = env.get_template('markdown_template.jinja2')
315
+ output(template.render({'module_bc': breaking_changes}))
316
+ return None
@@ -0,0 +1,28 @@
1
+ # Upcoming breaking changes in Azure CLI
2
+
3
+ {% for module, command_bc in module_bc.items() -%}
4
+ ## {{ module }}
5
+
6
+ {% for command, multi_version_bcs in command_bc.items() -%}
7
+ {% if not (module == 'core' and command == 'core') -%}
8
+ ### `{{ command }}`
9
+
10
+ {% endif -%}
11
+ {% if multi_version_bcs is mapping -%}
12
+ {% for version, bcs in multi_version_bcs | dictsort -%}
13
+ ###{%- if not (module == 'core' and command == 'core') -%}#{%- endif %} Deprecated in {{ version }}
14
+
15
+ {% for bc in bcs -%}
16
+ - {{ bc }}
17
+ {% endfor %}
18
+
19
+ {% endfor -%}
20
+ {% else -%}
21
+
22
+ {% for bc in multi_version_bcs -%}
23
+ - {{ bc }}
24
+ {% endfor %}
25
+
26
+ {% endif -%}
27
+ {% endfor -%}
28
+ {% endfor -%}
@@ -10,7 +10,8 @@ import time
10
10
 
11
11
  from knack.log import get_logger
12
12
  import azure_cli_diff_tool
13
- from azdev.utilities import display, require_azure_cli, heading, get_path_table, filter_by_git_diff
13
+ from azdev.utilities import display, require_azure_cli, heading, get_path_table, filter_by_git_diff, \
14
+ calc_selected_mod_names
14
15
  from .custom import DiffExportFormat, get_commands_meta, STORED_DEPRECATION_KEY
15
16
  from .util import export_commands_meta, dump_command_tree, add_to_command_tree
16
17
  from ..statistics import _create_invoker_and_load_cmds, _get_command_source, \
@@ -144,26 +145,7 @@ def cmp_command_meta(base_meta_file, diff_meta_file, only_break=False, output_ty
144
145
  def export_command_tree(modules, output_file=None):
145
146
  require_azure_cli()
146
147
 
147
- # allow user to run only on CLI or extensions
148
- cli_only = modules == ['CLI']
149
- ext_only = modules == ['EXT']
150
- if cli_only or ext_only:
151
- modules = None
152
-
153
- selected_modules = get_path_table(include_only=modules)
154
-
155
- if cli_only:
156
- selected_modules['ext'] = {}
157
- if ext_only:
158
- selected_modules['core'] = {}
159
- selected_modules['mod'] = {}
160
-
161
- if not any(selected_modules.values()):
162
- logger.warning('No commands selected to check.')
163
-
164
- selected_mod_names = list(selected_modules['mod'].keys())
165
- selected_mod_names += list(selected_modules['ext'].keys())
166
- selected_mod_names += list(selected_modules['core'].keys())
148
+ selected_mod_names = calc_selected_mod_names(modules)
167
149
 
168
150
  if selected_mod_names:
169
151
  display('Modules selected: {}\n'.format(', '.join(selected_mod_names)))
@@ -194,7 +194,7 @@ def _get_command_source(command_name, command):
194
194
  }
195
195
 
196
196
 
197
- def _create_invoker_and_load_cmds(cli_ctx):
197
+ def _create_invoker_and_load_cmds(cli_ctx, load_arguments=False):
198
198
  from knack.events import (
199
199
  EVENT_INVOKER_PRE_CMD_TBL_CREATE, EVENT_INVOKER_POST_CMD_TBL_CREATE)
200
200
  from azure.cli.core.commands import register_cache_arguments
@@ -215,9 +215,11 @@ def _create_invoker_and_load_cmds(cli_ctx):
215
215
  invoker.commands_loader.load_command_table(None)
216
216
  invoker.commands_loader.command_name = ''
217
217
 
218
- # cli_ctx.raise_event(EVENT_INVOKER_PRE_LOAD_ARGUMENTS, commands_loader=invoker.commands_loader)
219
- # invoker.commands_loader.load_arguments()
220
- # cli_ctx.raise_event(EVENT_INVOKER_POST_LOAD_ARGUMENTS, commands_loader=invoker.commands_loader)
218
+ if load_arguments:
219
+ from azure.cli.core.commands.events import EVENT_INVOKER_PRE_LOAD_ARGUMENTS, EVENT_INVOKER_POST_LOAD_ARGUMENTS
220
+ cli_ctx.raise_event(EVENT_INVOKER_PRE_LOAD_ARGUMENTS, commands_loader=invoker.commands_loader)
221
+ invoker.commands_loader.load_arguments()
222
+ cli_ctx.raise_event(EVENT_INVOKER_POST_LOAD_ARGUMENTS, commands_loader=invoker.commands_loader)
221
223
 
222
224
  cli_ctx.raise_event(EVENT_INVOKER_POST_CMD_TBL_CREATE, commands_loader=invoker.commands_loader)
223
225
  invoker.parser.cli_ctx = cli_ctx
@@ -256,3 +256,17 @@ def load_arguments(self, _):
256
256
  'If the base directory does not exist, it will be created')
257
257
  c.argument('output_type', choices=['xml', 'html', 'text', 'man', 'latex'], default="xml",
258
258
  help='Output type of the generated docs.')
259
+
260
+ with ArgumentsContext(self, 'generate-breaking-change-report') as c:
261
+ c.positional('modules', modules_type)
262
+ c.argument('target_version', default='NextWindow',
263
+ help='Only the breaking changes scheduled prior to the specified version will be displayed. '
264
+ 'The value could be `NextWindow`, `None` or a specified version like `3.0.0`')
265
+ c.argument('source', choices=['deprecate_info', 'pre_announce'], default='pre_announce',
266
+ help='The source of pre-announced breaking changes. `deprecate_info` represents all breaking changes '
267
+ 'marked through `deprecation_info`; `pre_announce` represents the breaking changes announced in '
268
+ '`breaking_change.py` file.')
269
+ c.argument('group_by_version', action='store_true',
270
+ help='If specified, breaking changes would be grouped by their target version as well.')
271
+ c.argument('output_format', choices=['structure', 'markdown'], default='structure',
272
+ help='Output format of the collected breaking changes.')
@@ -47,7 +47,8 @@ from .path import (
47
47
  get_cli_repo_path,
48
48
  get_ext_repo_paths,
49
49
  get_path_table,
50
- get_name_index
50
+ get_name_index,
51
+ calc_selected_mod_names
51
52
  )
52
53
  from .testing import test_cmd
53
54
  from .tools import (
@@ -93,4 +94,5 @@ __all__ = [
93
94
  'require_virtual_env',
94
95
  'require_azure_cli',
95
96
  'diff_branches_detail',
97
+ 'calc_selected_mod_names',
96
98
  ]
@@ -3,7 +3,7 @@
3
3
  # Licensed under the MIT License. See License.txt in the project root for
4
4
  # license information.
5
5
  # -----------------------------------------------------------------------------
6
-
6
+ import logging
7
7
  import os
8
8
  from glob import glob
9
9
 
@@ -11,6 +11,8 @@ from knack.util import CLIError
11
11
 
12
12
  from .const import COMMAND_MODULE_PREFIX, EXTENSION_PREFIX, ENV_VAR_VIRTUAL_ENV
13
13
 
14
+ logger = logging.getLogger(__name__)
15
+
14
16
 
15
17
  def extract_module_name(path):
16
18
 
@@ -261,3 +263,27 @@ def get_path_table(include_only=None, include_whl_extensions=False):
261
263
  raise CLIError('unrecognized modules: [ {} ]'.format(', '.join(include_only)))
262
264
 
263
265
  return table
266
+
267
+
268
+ def calc_selected_mod_names(modules=None):
269
+ # allow user to run only on CLI or extensions
270
+ cli_only = modules == ['CLI']
271
+ ext_only = modules == ['EXT']
272
+ if cli_only or ext_only:
273
+ modules = None
274
+
275
+ selected_modules = get_path_table(include_only=modules)
276
+
277
+ if cli_only:
278
+ selected_modules['ext'] = {}
279
+ if ext_only:
280
+ selected_modules['core'] = {}
281
+ selected_modules['mod'] = {}
282
+
283
+ if not any(selected_modules.values()):
284
+ logger.warning('No commands selected to check.')
285
+
286
+ selected_mod_names = list(selected_modules['mod'].keys())
287
+ selected_mod_names += list(selected_modules['ext'].keys())
288
+ selected_mod_names += list(selected_modules['core'].keys())
289
+ return selected_mod_names
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: azdev
3
- Version: 0.1.81
3
+ Version: 0.1.83
4
4
  Summary: Microsoft Azure CLI Developer Tools
5
5
  Home-page: https://github.com/Azure/azure-cli-dev-tools
6
6
  Author: Microsoft Corporation
@@ -148,6 +148,14 @@ License
148
148
 
149
149
  Release History
150
150
  ===============
151
+ 0.1.83
152
+ ++++++
153
+ * `azdev generate-breaking-change-report`: Fix `azdev.operations.breaking_change` not included in `setup.py`.
154
+
155
+ 0.1.82
156
+ ++++++
157
+ * `azdev generate-breaking-change-report`: New command to collect upcoming breaking changes from codebase.
158
+
151
159
  0.1.81
152
160
  ++++++
153
161
  * `azdev scan/mask`: Add `--confidence-level` to support secret pattern levels
@@ -51,6 +51,8 @@ azdev/operations/resource.py
51
51
  azdev/operations/secret.py
52
52
  azdev/operations/setup.py
53
53
  azdev/operations/style.py
54
+ azdev/operations/breaking_change/__init__.py
55
+ azdev/operations/breaking_change/markdown_template.jinja2
54
56
  azdev/operations/cmdcov/__init__.py
55
57
  azdev/operations/cmdcov/_macros.j2
56
58
  azdev/operations/cmdcov/cmdcov.py
@@ -62,6 +62,7 @@ setup(
62
62
  'azdev.operations.extensions',
63
63
  'azdev.operations.statistics',
64
64
  'azdev.operations.command_change',
65
+ 'azdev.operations.breaking_change',
65
66
  'azdev.operations.cmdcov',
66
67
  'azdev.utilities',
67
68
  ],
@@ -93,6 +94,7 @@ setup(
93
94
  'azdev.mod_templates': ['*.*'],
94
95
  'azdev.operations.linter.rules': ['ci_exclusions.yml'],
95
96
  'azdev.operations.cmdcov': ['*.*'],
97
+ 'azdev.operations.breaking_change': ['*.*'],
96
98
  },
97
99
  include_package_data=True,
98
100
  entry_points={
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes