azdev 0.2.9__tar.gz → 0.2.12__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 (107) hide show
  1. {azdev-0.2.9 → azdev-0.2.12}/HISTORY.rst +26 -1
  2. {azdev-0.2.9/azdev.egg-info → azdev-0.2.12}/PKG-INFO +32 -7
  3. {azdev-0.2.9 → azdev-0.2.12}/azdev/__init__.py +1 -1
  4. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/code_gen.py +7 -2
  5. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/extensions/__init__.py +37 -3
  6. azdev-0.2.12/azdev/operations/extensions/metadata.py +203 -0
  7. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/extensions/util.py +5 -5
  8. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/setup.py +19 -13
  9. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/testtool/pytest_runner.py +5 -4
  10. {azdev-0.2.9 → azdev-0.2.12}/azdev/utilities/__init__.py +2 -0
  11. {azdev-0.2.9 → azdev-0.2.12}/azdev/utilities/command.py +20 -0
  12. {azdev-0.2.9 → azdev-0.2.12/azdev.egg-info}/PKG-INFO +32 -7
  13. {azdev-0.2.9 → azdev-0.2.12}/azdev.egg-info/SOURCES.txt +1 -0
  14. {azdev-0.2.9 → azdev-0.2.12}/azdev.egg-info/requires.txt +3 -3
  15. {azdev-0.2.9 → azdev-0.2.12}/setup.py +9 -6
  16. {azdev-0.2.9 → azdev-0.2.12}/LICENSE +0 -0
  17. {azdev-0.2.9 → azdev-0.2.12}/MANIFEST.in +0 -0
  18. {azdev-0.2.9 → azdev-0.2.12}/README.md +0 -0
  19. {azdev-0.2.9 → azdev-0.2.12}/README.rst +0 -0
  20. {azdev-0.2.9 → azdev-0.2.12}/azdev/__main__.py +0 -0
  21. {azdev-0.2.9 → azdev-0.2.12}/azdev/commands.py +0 -0
  22. {azdev-0.2.9 → azdev-0.2.12}/azdev/completer.py +0 -0
  23. {azdev-0.2.9 → azdev-0.2.12}/azdev/config/__init__.py +0 -0
  24. {azdev-0.2.9 → azdev-0.2.12}/azdev/config/cli.flake8 +0 -0
  25. {azdev-0.2.9 → azdev-0.2.12}/azdev/config/cli_pylintrc +0 -0
  26. {azdev-0.2.9 → azdev-0.2.12}/azdev/config/ext.flake8 +0 -0
  27. {azdev-0.2.9 → azdev-0.2.12}/azdev/config/ext_pylintrc +0 -0
  28. {azdev-0.2.9 → azdev-0.2.12}/azdev/help.py +0 -0
  29. {azdev-0.2.9 → azdev-0.2.12}/azdev/mod_templates/HISTORY.rst +0 -0
  30. {azdev-0.2.9 → azdev-0.2.12}/azdev/mod_templates/README.rst +0 -0
  31. {azdev-0.2.9 → azdev-0.2.12}/azdev/mod_templates/_client_factory.py +0 -0
  32. {azdev-0.2.9 → azdev-0.2.12}/azdev/mod_templates/_help.py +0 -0
  33. {azdev-0.2.9 → azdev-0.2.12}/azdev/mod_templates/_params.py +0 -0
  34. {azdev-0.2.9 → azdev-0.2.12}/azdev/mod_templates/_validators.py +0 -0
  35. {azdev-0.2.9 → azdev-0.2.12}/azdev/mod_templates/azext_metadata.json +0 -0
  36. {azdev-0.2.9 → azdev-0.2.12}/azdev/mod_templates/blank__init__.py +0 -0
  37. {azdev-0.2.9 → azdev-0.2.12}/azdev/mod_templates/commands.py +0 -0
  38. {azdev-0.2.9 → azdev-0.2.12}/azdev/mod_templates/custom.py +0 -0
  39. {azdev-0.2.9 → azdev-0.2.12}/azdev/mod_templates/module__init__.py +0 -0
  40. {azdev-0.2.9 → azdev-0.2.12}/azdev/mod_templates/pkg_declare__init__.py +0 -0
  41. {azdev-0.2.9 → azdev-0.2.12}/azdev/mod_templates/setup.cfg +0 -0
  42. {azdev-0.2.9 → azdev-0.2.12}/azdev/mod_templates/setup.py +0 -0
  43. {azdev-0.2.9 → azdev-0.2.12}/azdev/mod_templates/test_service_scenario.py +0 -0
  44. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/__init__.py +0 -0
  45. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/breaking_change/__init__.py +0 -0
  46. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/breaking_change/markdown_template.jinja2 +0 -0
  47. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/cmdcov/__init__.py +0 -0
  48. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/cmdcov/_macros.j2 +0 -0
  49. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/cmdcov/cmdcov.py +0 -0
  50. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/cmdcov/component.css +0 -0
  51. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/cmdcov/component.js +0 -0
  52. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/cmdcov/favicon.ico +0 -0
  53. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/cmdcov/index.j2 +0 -0
  54. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/cmdcov/index2.j2 +0 -0
  55. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/cmdcov/module.j2 +0 -0
  56. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/command_change/__init__.py +0 -0
  57. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/command_change/custom.py +0 -0
  58. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/command_change/util.py +0 -0
  59. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/constant.py +0 -0
  60. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/extensions/version_upgrade.py +0 -0
  61. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/help/__init__.py +0 -0
  62. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/help/refdoc/__init__.py +0 -0
  63. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/help/refdoc/conf.py +0 -0
  64. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/latest_index.py +0 -0
  65. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/legal.py +0 -0
  66. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/linter/__init__.py +0 -0
  67. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/linter/data/cmd_example_config.json +0 -0
  68. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/linter/linter.py +0 -0
  69. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/linter/pylint_checkers/__init__.py +0 -0
  70. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/linter/pylint_checkers/show_command.py +0 -0
  71. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/linter/rule_decorators.py +0 -0
  72. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/linter/rules/__init__.py +0 -0
  73. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/linter/rules/ci_exclusions.yml +0 -0
  74. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/linter/rules/command_coverage_rules.py +0 -0
  75. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/linter/rules/command_group_rules.py +0 -0
  76. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/linter/rules/command_rules.py +0 -0
  77. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/linter/rules/help_rules.py +0 -0
  78. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/linter/rules/linter_exclusions.yml +0 -0
  79. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/linter/rules/parameter_rules.py +0 -0
  80. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/linter/util.py +0 -0
  81. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/performance.py +0 -0
  82. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/pypi.py +0 -0
  83. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/python_sdk.py +0 -0
  84. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/regex.py +0 -0
  85. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/resource.py +0 -0
  86. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/secret.py +0 -0
  87. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/statistics/__init__.py +0 -0
  88. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/statistics/util.py +0 -0
  89. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/style.py +0 -0
  90. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/testtool/__init__.py +0 -0
  91. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/testtool/incremental_strategy.py +0 -0
  92. {azdev-0.2.9 → azdev-0.2.12}/azdev/operations/testtool/profile_context.py +0 -0
  93. {azdev-0.2.9 → azdev-0.2.12}/azdev/params.py +0 -0
  94. {azdev-0.2.9 → azdev-0.2.12}/azdev/transformers.py +0 -0
  95. {azdev-0.2.9 → azdev-0.2.12}/azdev/utilities/config.py +0 -0
  96. {azdev-0.2.9 → azdev-0.2.12}/azdev/utilities/const.py +0 -0
  97. {azdev-0.2.9 → azdev-0.2.12}/azdev/utilities/display.py +0 -0
  98. {azdev-0.2.9 → azdev-0.2.12}/azdev/utilities/git_util.py +0 -0
  99. {azdev-0.2.9 → azdev-0.2.12}/azdev/utilities/path.py +0 -0
  100. {azdev-0.2.9 → azdev-0.2.12}/azdev/utilities/pypi.py +0 -0
  101. {azdev-0.2.9 → azdev-0.2.12}/azdev/utilities/testing.py +0 -0
  102. {azdev-0.2.9 → azdev-0.2.12}/azdev/utilities/tools.py +0 -0
  103. {azdev-0.2.9 → azdev-0.2.12}/azdev.egg-info/dependency_links.txt +0 -0
  104. {azdev-0.2.9 → azdev-0.2.12}/azdev.egg-info/entry_points.txt +0 -0
  105. {azdev-0.2.9 → azdev-0.2.12}/azdev.egg-info/top_level.txt +0 -0
  106. {azdev-0.2.9 → azdev-0.2.12}/pyproject.toml +0 -0
  107. {azdev-0.2.9 → azdev-0.2.12}/setup.cfg +0 -0
@@ -2,12 +2,37 @@
2
2
 
3
3
  Release History
4
4
  ===============
5
+ 0.2.12
6
+ ++++++
7
+ * Editable installs (``azdev setup``, ``azdev extension add``, code generation) now pass ``--no-build-isolation`` so wheels build against the environment's pinned ``setuptools`` instead of an isolated PEP 517 build environment. This fixes editable-install failures introduced by the ``setuptools`` pinning.
8
+
9
+ 0.2.12b3
10
+ ++++++++
11
+ * ``azdev extension update-index``/``publish``: Fix ``ValueError: Not a known wheel archive format`` when reading wheel metadata. The wheel is now downloaded with its ``.whl`` filename so ``pkginfo.Wheel`` can read it, and ``read_pkginfo`` falls back to a temporary ``.whl`` copy for any non-``.whl`` path. (#7740)
12
+
13
+ 0.2.12b2
14
+ ++++++++
15
+ * Quote paths when running editable installs (``pip install -e <path>``) and the pytest runner so that paths containing spaces (e.g. OneDrive folders) no longer break ``azdev extension add``, ``azdev setup``, code generation, and ``azdev test``. (#550, #415)
16
+
17
+ 0.2.12b1
18
+ ++++++++
19
+ * Extract extension metadata generation logic and decouple from ``wheel==0.30.0``; read wheel ``METADATA`` via ``pkginfo`` instead of the legacy ``metadata.json`` artifact. Drops the ``wheel==0.30.0`` and ``setuptools==70.0.0`` pins. (#521)
20
+
21
+ 0.2.11
22
+ ++++++
23
+ * `azdev extension add/remove`: Invalidate command index after installing or removing extensions.
24
+
25
+ 0.2.10
26
+ ++++++
27
+ * Add support for Python 3.14 and drop support for Python 3.9
28
+ * Bump pylint constraint to >=4,<5 for Python 3.14 and style compatibility
29
+
5
30
  0.2.9
6
31
  +++++
7
32
  * `azdev latest-index`: Add `generate` and `verify` commands to manage Azure CLI packaged latest indices (`commandIndex.latest.json`, `helpIndex.latest.json`) with CI-friendly verify exit behavior.
8
33
 
9
34
  0.2.8
10
- ++++++
35
+ +++++
11
36
  * Pin pip to 25.2 as pip 25.3 remove support for the legacy setup.py develop editable method in setuptools editable installs; setuptools >= 64 is now required. (#11457)
12
37
 
13
38
  0.2.7
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: azdev
3
- Version: 0.2.9
3
+ Version: 0.2.12
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
@@ -13,12 +13,12 @@ Classifier: Topic :: Software Development :: Build Tools
13
13
  Classifier: Environment :: Console
14
14
  Classifier: License :: OSI Approved :: MIT License
15
15
  Classifier: Natural Language :: English
16
- Classifier: Programming Language :: Python :: 3.9
17
16
  Classifier: Programming Language :: Python :: 3.10
18
17
  Classifier: Programming Language :: Python :: 3.11
19
18
  Classifier: Programming Language :: Python :: 3.12
20
19
  Classifier: Programming Language :: Python :: 3.13
21
- Requires-Python: >=3.9
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Requires-Python: >=3.10
22
22
  License-File: LICENSE
23
23
  Requires-Dist: azure-multiapi-storage
24
24
  Requires-Dist: docutils
@@ -26,7 +26,7 @@ Requires-Dist: flake8
26
26
  Requires-Dist: gitpython
27
27
  Requires-Dist: jinja2
28
28
  Requires-Dist: knack
29
- Requires-Dist: pylint<4
29
+ Requires-Dist: pylint<5,>=4
30
30
  Requires-Dist: pytest-xdist
31
31
  Requires-Dist: pytest-forked
32
32
  Requires-Dist: pytest>=5.0.0
@@ -38,9 +38,9 @@ Requires-Dist: jsbeautifier~=1.14.7
38
38
  Requires-Dist: deepdiff~=8.6.1
39
39
  Requires-Dist: azure-cli-diff-tool~=0.1.1
40
40
  Requires-Dist: packaging
41
+ Requires-Dist: pkginfo
41
42
  Requires-Dist: tqdm
42
- Requires-Dist: wheel==0.30.0
43
- Requires-Dist: setuptools==70.0.0
43
+ Requires-Dist: setuptools<80,>=78.1.1
44
44
  Requires-Dist: microsoft-security-utilities-secret-masker~=1.0.0b4
45
45
  Dynamic: author
46
46
  Dynamic: author-email
@@ -203,12 +203,37 @@ License
203
203
 
204
204
  Release History
205
205
  ===============
206
+ 0.2.12
207
+ ++++++
208
+ * Editable installs (``azdev setup``, ``azdev extension add``, code generation) now pass ``--no-build-isolation`` so wheels build against the environment's pinned ``setuptools`` instead of an isolated PEP 517 build environment. This fixes editable-install failures introduced by the ``setuptools`` pinning.
209
+
210
+ 0.2.12b3
211
+ ++++++++
212
+ * ``azdev extension update-index``/``publish``: Fix ``ValueError: Not a known wheel archive format`` when reading wheel metadata. The wheel is now downloaded with its ``.whl`` filename so ``pkginfo.Wheel`` can read it, and ``read_pkginfo`` falls back to a temporary ``.whl`` copy for any non-``.whl`` path. (#7740)
213
+
214
+ 0.2.12b2
215
+ ++++++++
216
+ * Quote paths when running editable installs (``pip install -e <path>``) and the pytest runner so that paths containing spaces (e.g. OneDrive folders) no longer break ``azdev extension add``, ``azdev setup``, code generation, and ``azdev test``. (#550, #415)
217
+
218
+ 0.2.12b1
219
+ ++++++++
220
+ * Extract extension metadata generation logic and decouple from ``wheel==0.30.0``; read wheel ``METADATA`` via ``pkginfo`` instead of the legacy ``metadata.json`` artifact. Drops the ``wheel==0.30.0`` and ``setuptools==70.0.0`` pins. (#521)
221
+
222
+ 0.2.11
223
+ ++++++
224
+ * `azdev extension add/remove`: Invalidate command index after installing or removing extensions.
225
+
226
+ 0.2.10
227
+ ++++++
228
+ * Add support for Python 3.14 and drop support for Python 3.9
229
+ * Bump pylint constraint to >=4,<5 for Python 3.14 and style compatibility
230
+
206
231
  0.2.9
207
232
  +++++
208
233
  * `azdev latest-index`: Add `generate` and `verify` commands to manage Azure CLI packaged latest indices (`commandIndex.latest.json`, `helpIndex.latest.json`) with CI-friendly verify exit behavior.
209
234
 
210
235
  0.2.8
211
- ++++++
236
+ +++++
212
237
  * Pin pip to 25.2 as pip 25.3 remove support for the legacy setup.py develop editable method in setuptools editable installs; setuptools >= 64 is now required. (#11457)
213
238
 
214
239
  0.2.7
@@ -4,4 +4,4 @@
4
4
  # license information.
5
5
  # -----------------------------------------------------------------------------
6
6
 
7
- __VERSION__ = '0.2.9'
7
+ __VERSION__ = '0.2.12'
@@ -14,10 +14,12 @@ from knack.util import CLIError
14
14
 
15
15
  from azdev.utilities import (
16
16
  pip_cmd, display, heading, COMMAND_MODULE_PREFIX, EXTENSION_PREFIX, get_cli_repo_path, get_ext_repo_paths,
17
- find_files)
17
+ find_files, quote_arg)
18
18
 
19
19
  logger = get_logger(__name__)
20
20
 
21
+ _PIP_EDITABLE_OPTS = "--config-settings editable_mode=compat --no-build-isolation"
22
+
21
23
  _MODULE_ROOT_PATH = os.path.join('src', 'azure-cli', 'azure', 'cli', 'command_modules')
22
24
 
23
25
 
@@ -297,6 +299,9 @@ def _create_package(prefix, repo_path, is_ext, name='test', display_name=None, d
297
299
  _generate_files(env, kwargs, test_files, dest_path)
298
300
 
299
301
  if is_ext:
300
- result = pip_cmd('install -e {}'.format(new_package_path), "Installing `{}{}`...".format(prefix, name))
302
+ result = pip_cmd(
303
+ 'install -e {} {}'.format(quote_arg(new_package_path), _PIP_EDITABLE_OPTS),
304
+ "Installing `{}{}`...".format(prefix, name),
305
+ )
301
306
  if result.error:
302
307
  raise result.error # pylint: disable=raising-bad-type
@@ -16,11 +16,38 @@ from knack.util import CLIError
16
16
 
17
17
  from azdev.utilities import (
18
18
  cmd, py_cmd, pip_cmd, display, get_ext_repo_paths, find_files, get_azure_config, get_azdev_config,
19
- require_azure_cli, heading, subheading, EXTENSION_PREFIX)
19
+ get_azure_config_dir, require_azure_cli, heading, subheading, quote_arg, EXTENSION_PREFIX)
20
20
  from .version_upgrade import VersionUpgradeMod
21
21
 
22
22
  logger = get_logger(__name__)
23
23
 
24
+ _PIP_EDITABLE_OPTS = "--config-settings editable_mode=compat --no-build-isolation"
25
+
26
+ # These are the index files cleared by CommandIndex().invalidate() in azure-cli-core.
27
+ # Refer: azure-cli-core/azure/cli/core/__init__.py
28
+ _COMMAND_INDEX_FILES = (
29
+ 'commandIndex.json',
30
+ 'extensionIndex.json',
31
+ 'helpIndex.json',
32
+ 'extensionHelpIndex.json',
33
+ )
34
+
35
+
36
+ def _invalidate_command_index():
37
+ """Delete the CLI command index files so they are regenerated on next invocation.
38
+
39
+ This mirrors the behavior of ``CommandIndex().invalidate()`` in azure-cli-core but
40
+ works without requiring a fully initialized CLI session.
41
+ """
42
+ azure_config_dir = get_azure_config_dir()
43
+ for filename in _COMMAND_INDEX_FILES:
44
+ filepath = os.path.join(azure_config_dir, filename)
45
+ try:
46
+ os.remove(filepath)
47
+ logger.debug("Deleted command index file: %s", filepath)
48
+ except OSError:
49
+ pass
50
+
24
51
 
25
52
  def add_extension(extensions):
26
53
 
@@ -45,10 +72,15 @@ def add_extension(extensions):
45
72
  raise CLIError('extension(s) not found: {}'.format(' '.join(extensions)))
46
73
 
47
74
  for path in paths_to_add:
48
- result = pip_cmd('install -e {}'.format(path), "Adding extension '{}'...".format(path))
75
+ result = pip_cmd(
76
+ 'install -e {} {}'.format(quote_arg(path), _PIP_EDITABLE_OPTS),
77
+ "Adding extension '{}'...".format(path),
78
+ )
49
79
  if result.error:
50
80
  raise result.error # pylint: disable=raising-bad-type
51
81
 
82
+ _invalidate_command_index()
83
+
52
84
 
53
85
  def remove_extension(extensions):
54
86
 
@@ -83,6 +115,8 @@ def remove_extension(extensions):
83
115
  display("Removing '{}'...".format(path_to_remove))
84
116
  shutil.rmtree(path_to_remove)
85
117
 
118
+ _invalidate_command_index()
119
+
86
120
 
87
121
  def _get_installed_dev_extensions(dev_sources):
88
122
  from glob import glob
@@ -254,7 +288,7 @@ def update_extension_index(extensions):
254
288
  ext_dir = tempfile.mkdtemp(dir=extensions_dir)
255
289
  whl_cache_dir = tempfile.mkdtemp()
256
290
  whl_cache = {}
257
- ext_file = get_whl_from_url(ext_path, extension_name, whl_cache_dir, whl_cache)
291
+ ext_file = get_whl_from_url(ext_path, ext_path.split("/")[-1], whl_cache_dir, whl_cache)
258
292
 
259
293
  with open(index_path, 'r') as infile:
260
294
  curr_index = json.loads(infile.read())
@@ -0,0 +1,203 @@
1
+ # --------------------------------------------------------------------------------------------
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # Licensed under the MIT License. See License.txt in the project root for license information.
4
+ # --------------------------------------------------------------------------------------------
5
+
6
+ """
7
+ Extension metadata extraction.
8
+
9
+ Replaces the legacy wheel-0.30.0 ``metadata.json`` read path with a
10
+ ``pkginfo``-based reader of the spec-compliant ``METADATA`` file inside each
11
+ extension wheel, merged with the extension's ``azext_metadata.json``.
12
+
13
+ Used by ``azdev.operations.extensions.util.get_ext_metadata`` to build the
14
+ entries stored in ``index.json``.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import re
21
+ from pathlib import Path
22
+ from typing import Any, Dict, List, Optional, Tuple
23
+
24
+
25
+ # Splits a requirement string into (name, spec). Accepts both shapes that may
26
+ # appear in METADATA Requires-Dist:
27
+ # * PEP 508 form: "oras==0.1.30" (modern setuptools / wheel)
28
+ # * PEP 314 form: "oras (==0.1.30)" (older setuptools, wheel 0.30.0)
29
+ # Either spec form is captured into a single, normalized spec string.
30
+ _REQ_SPLIT_RE = re.compile(
31
+ r"^\s*(?P<name>[A-Za-z0-9_.\-]+)\s*"
32
+ r"(?:\(\s*(?P<paren_spec>[^)]+?)\s*\)|(?P<bare_spec>[<>=!~].*?))?\s*$"
33
+ )
34
+
35
+
36
+ def _get_extension_modname(ext_dir: Path) -> str:
37
+ pos = [d.name for d in ext_dir.iterdir() if d.is_dir() and d.name.startswith("azext_")]
38
+ if len(pos) != 1:
39
+ raise AssertionError(
40
+ "Expected exactly one azext_* module in {}, found: {}".format(ext_dir, pos)
41
+ )
42
+ return pos[0]
43
+
44
+
45
+ def read_azext_metadata(ext_dir: Path) -> Dict[str, Any]:
46
+ modname = _get_extension_modname(ext_dir)
47
+ path = ext_dir / modname / "azext_metadata.json"
48
+ if not path.is_file():
49
+ return {}
50
+ with path.open(encoding="utf-8") as fh:
51
+ return json.load(fh)
52
+
53
+
54
+ def pkginfo_to_dict(ext_file) -> Dict[str, Any]:
55
+ """Build an index.json-shaped metadata dict from a wheel file.
56
+
57
+ This replaces the legacy ``metadata.json`` read path (which only existed
58
+ in wheels produced by ``wheel==0.30.0``) with a ``pkginfo.Wheel`` based
59
+ reader of the spec-defined ``METADATA`` file. Used by
60
+ ``azdev.operations.extensions.util.get_ext_metadata``.
61
+ """
62
+ return merge_to_index_metadata(read_pkginfo(Path(str(ext_file))), {})
63
+
64
+
65
+ def read_pkginfo(wheel_path: Path) -> Dict[str, Any]:
66
+ """Read spec-defined wheel metadata via pkginfo.Wheel."""
67
+ import pkginfo
68
+
69
+ wheel_path = Path(str(wheel_path))
70
+ target = wheel_path
71
+ if wheel_path.suffix != ".whl":
72
+ import shutil
73
+ import tempfile
74
+ target = Path(tempfile.mkdtemp()) / (wheel_path.name + ".whl")
75
+ shutil.copyfile(wheel_path, target)
76
+
77
+ whl = pkginfo.Wheel(str(target))
78
+ return {
79
+ "name": whl.name,
80
+ "version": whl.version,
81
+ "summary": whl.summary,
82
+ "description": whl.description,
83
+ "description_content_type": whl.description_content_type,
84
+ "license": whl.license,
85
+ "classifiers": list(whl.classifiers or []),
86
+ "requires_dist": list(whl.requires_dist or []),
87
+ "requires_python": whl.requires_python,
88
+ "author": whl.author,
89
+ "author_email": whl.author_email,
90
+ "home_page": whl.home_page,
91
+ "project_urls": list(whl.project_urls or []),
92
+ "metadata_version": whl.metadata_version,
93
+ "keywords": whl.keywords,
94
+ }
95
+
96
+
97
+ def _coerce_run_requires(requires_dist: List[str]) -> List[Dict[str, Any]]:
98
+ """Approximate the legacy `run_requires` block produced by wheel 0.30.0.
99
+
100
+ Wheel 0.30.0 emitted each requirement in two forms inside `run_requires`:
101
+ * the PEP 314 / PEP 345 form: ``"oras (==0.1.30)"`` (name space then
102
+ version specifier wrapped in parentheses), and
103
+ * the canonical PEP 508 form: ``"oras==0.1.30"`` (no space, no parens).
104
+
105
+ It also sorted entries alphabetically by package name (this is observable in
106
+ `src/index.json`: every `run_requires` block is name-sorted regardless of
107
+ `install_requires` order in `setup.py`).
108
+
109
+ Modern wheel metadata (`METADATA` Requires-Dist) only carries PEP 508 and
110
+ preserves source order, so we reproduce both transformations here.
111
+ """
112
+ if not requires_dist:
113
+ return []
114
+
115
+ parsed: List[Tuple[str, Optional[str], str]] = []
116
+ seen: set = set()
117
+ for req in requires_dist:
118
+ canonical = req.strip()
119
+ match = _REQ_SPLIT_RE.match(canonical)
120
+ if match:
121
+ name = match.group("name")
122
+ spec = match.group("paren_spec") or match.group("bare_spec")
123
+ spec = spec.strip() if spec else None
124
+ else:
125
+ name, spec = canonical, None
126
+ # Older setuptools (e.g. 70.0.0) writes Requires-Dist twice per
127
+ # package in METADATA -- once as "name (spec)" and once as
128
+ # "name==spec". Modern setuptools writes only the canonical PEP 508
129
+ # form. Deduplicate on (lowercase name, normalized spec) so the
130
+ # doubling step below produces the same output regardless of which
131
+ # setuptools generated the wheel.
132
+ key = (name.lower(), (spec or "").replace(" ", ""))
133
+ if key in seen:
134
+ continue
135
+ seen.add(key)
136
+ parsed.append((name, spec, canonical))
137
+
138
+ parsed.sort(key=lambda t: t[0].lower())
139
+
140
+ doubled: List[str] = []
141
+ for name, spec, canonical in parsed:
142
+ if spec:
143
+ doubled.append("{} ({})".format(name, spec))
144
+ doubled.append("{}{}".format(name, spec))
145
+ else:
146
+ doubled.append(canonical)
147
+ doubled.append(canonical)
148
+ return [{"requires": doubled}]
149
+
150
+
151
+ def _coerce_project_urls(project_urls: List[str], home_page: Optional[str]) -> Dict[str, str]:
152
+ out: Dict[str, str] = {}
153
+ if home_page:
154
+ out["Home"] = home_page
155
+ for entry in project_urls or []:
156
+ if "," in entry:
157
+ label, url = entry.split(",", 1)
158
+ out[label.strip()] = url.strip()
159
+ return out
160
+
161
+
162
+ def _coerce_contacts(author: Optional[str], author_email: Optional[str]) -> List[Dict[str, str]]:
163
+ if not author and not author_email:
164
+ return []
165
+ contact: Dict[str, str] = {"role": "author"}
166
+ if author:
167
+ contact["name"] = author
168
+ if author_email:
169
+ contact["email"] = author_email
170
+ return [contact]
171
+
172
+
173
+ def merge_to_index_metadata(pkg: Dict[str, Any], azext: Dict[str, Any]) -> Dict[str, Any]:
174
+ """Merge `pkginfo` output and `azext_metadata.json` into the index.json shape.
175
+
176
+ Precedence (highest first): azext_metadata > pkginfo > derived defaults.
177
+ """
178
+ metadata: Dict[str, Any] = {}
179
+
180
+ metadata["name"] = pkg.get("name")
181
+ metadata["version"] = pkg.get("version")
182
+ metadata["summary"] = pkg.get("summary")
183
+ metadata["license"] = pkg.get("license")
184
+ metadata["metadata_version"] = pkg.get("metadata_version")
185
+ metadata["classifiers"] = pkg.get("classifiers") or []
186
+ metadata["extras"] = []
187
+ metadata["run_requires"] = _coerce_run_requires(pkg.get("requires_dist") or [])
188
+ metadata["requires_python"] = pkg.get("requires_python")
189
+ metadata["description_content_type"] = pkg.get("description_content_type")
190
+
191
+ contacts = _coerce_contacts(pkg.get("author"), pkg.get("author_email"))
192
+ project_urls = _coerce_project_urls(pkg.get("project_urls") or [], pkg.get("home_page"))
193
+ details: Dict[str, Any] = {}
194
+ if contacts:
195
+ details["contacts"] = contacts
196
+ if project_urls:
197
+ details["project_urls"] = project_urls
198
+ if details:
199
+ metadata["extensions"] = {"python.details": details}
200
+
201
+ metadata.update(azext)
202
+
203
+ return {k: v for k, v in metadata.items() if v is not None}
@@ -12,6 +12,7 @@ import zipfile
12
12
  from knack.util import CLIError
13
13
 
14
14
  from azdev.utilities import EXTENSION_PREFIX
15
+ from azdev.operations.extensions.metadata import pkginfo_to_dict
15
16
 
16
17
 
17
18
  WHEEL_INFO_RE = re.compile(
@@ -45,7 +46,9 @@ def _get_azext_metadata(ext_dir):
45
46
 
46
47
  def get_ext_metadata(ext_dir, ext_file, ext_name):
47
48
  # Modification of https://github.com/Azure/azure-cli/blob/dev/src/azure-cli-core/azure/cli/core/extension.py#L89
48
- WHL_METADATA_FILENAME = 'metadata.json'
49
+ # Read spec-defined wheel metadata via pkginfo so we don't depend on the
50
+ # legacy wheel-0.30.0 only ``metadata.json`` artifact.
51
+ generated_metadata = pkginfo_to_dict(ext_file)
49
52
  with zipfile.ZipFile(ext_file, 'r') as zip_ref:
50
53
  zip_ref.extractall(ext_dir)
51
54
  metadata = {}
@@ -56,10 +59,7 @@ def get_ext_metadata(ext_dir, ext_file, ext_name):
56
59
  for dist_info_dirname in dist_info_dirs:
57
60
  parsed_dist_info_dir = WHEEL_INFO_RE(dist_info_dirname)
58
61
  if parsed_dist_info_dir and parsed_dist_info_dir.groupdict().get('name') == ext_name.replace('-', '_'):
59
- whl_metadata_filepath = os.path.join(ext_dir, dist_info_dirname, WHL_METADATA_FILENAME)
60
- if os.path.isfile(whl_metadata_filepath):
61
- with open(whl_metadata_filepath) as f:
62
- metadata.update(json.load(f))
62
+ metadata.update(generated_metadata)
63
63
  return metadata
64
64
 
65
65
 
@@ -17,10 +17,12 @@ from azdev.operations.extensions import (
17
17
  from azdev.params import Flag
18
18
  from azdev.utilities import (
19
19
  display, heading, subheading, pip_cmd, CommandError, find_file,
20
- get_azdev_config_dir, get_azdev_config, require_virtual_env, get_azure_config)
20
+ get_azdev_config_dir, get_azdev_config, require_virtual_env, get_azure_config, quote_arg)
21
21
 
22
22
  logger = get_logger(__name__)
23
23
 
24
+ _PIP_EDITABLE_OPTS = "--config-settings editable_mode=compat --no-build-isolation"
25
+
24
26
 
25
27
  def _check_path(path, file_name):
26
28
  """ Ensures the file_name is provided in the supplied path. """
@@ -49,7 +51,8 @@ def _install_extensions(ext_paths):
49
51
 
50
52
  # install specified extensions
51
53
  for path in ext_paths or []:
52
- result = pip_cmd('install -e {}'.format(path), "Adding extension '{}'...".format(path))
54
+ result = pip_cmd('install -e {} {}'.format(quote_arg(path), _PIP_EDITABLE_OPTS),
55
+ "Adding extension '{}'...".format(path))
53
56
  if result.error:
54
57
  raise result.error # pylint: disable=raising-bad-type
55
58
 
@@ -75,13 +78,13 @@ def _install_cli(cli_path, deps=None):
75
78
  privates_dir = os.path.join(cli_path, "privates")
76
79
  if os.path.isdir(privates_dir) and os.listdir(privates_dir):
77
80
  whl_list = " ".join(
78
- [os.path.join(privates_dir, f) for f in os.listdir(privates_dir)]
81
+ [quote_arg(os.path.join(privates_dir, f)) for f in os.listdir(privates_dir)]
79
82
  )
80
83
  pip_cmd("install {}".format(whl_list), "Installing private whl files...")
81
84
 
82
85
  # install general requirements
83
86
  pip_cmd(
84
- "install -r {}".format(os.path.join(cli_path, "requirements.txt")),
87
+ "install -r {}".format(quote_arg(os.path.join(cli_path, "requirements.txt"))),
85
88
  "Installing `requirements.txt`..."
86
89
  )
87
90
 
@@ -90,38 +93,41 @@ def _install_cli(cli_path, deps=None):
90
93
  # Resolve dependencies from setup.py files.
91
94
  # command modules have dependency on azure-cli-core so install this first
92
95
  pip_cmd(
93
- "install -e {}".format(os.path.join(cli_src, 'azure-cli-telemetry')),
96
+ "install -e {} {}".format(quote_arg(os.path.join(cli_src, 'azure-cli-telemetry')), _PIP_EDITABLE_OPTS),
94
97
  "Installing `azure-cli-telemetry`..."
95
98
  )
96
99
  pip_cmd(
97
- "install -e {}".format(os.path.join(cli_src, 'azure-cli-core')),
100
+ "install -e {} {}".format(quote_arg(os.path.join(cli_src, 'azure-cli-core')), _PIP_EDITABLE_OPTS),
98
101
  "Installing `azure-cli-core`..."
99
102
  )
100
103
 
101
104
  # azure cli has dependencies on the above packages so install this one last
102
105
  pip_cmd(
103
- "install -e {}".format(os.path.join(cli_src, 'azure-cli')),
106
+ "install -e {} {}".format(quote_arg(os.path.join(cli_src, 'azure-cli')), _PIP_EDITABLE_OPTS),
104
107
  "Installing `azure-cli`..."
105
108
  )
106
109
 
107
110
  pip_cmd(
108
- "install -e {}".format(os.path.join(cli_src, 'azure-cli-testsdk')),
111
+ "install -e {} {}".format(quote_arg(os.path.join(cli_src, 'azure-cli-testsdk')), _PIP_EDITABLE_OPTS),
109
112
  "Installing `azure-cli-testsdk`..."
110
113
  )
111
114
  else:
112
115
  # First install packages without dependencies,
113
116
  # then resolve dependencies from requirements.*.txt file.
114
117
  pip_cmd(
115
- "install -e {} --no-deps".format(os.path.join(cli_src, 'azure-cli-telemetry')),
118
+ "install -e {} --no-deps {}".format(
119
+ quote_arg(os.path.join(cli_src, 'azure-cli-telemetry')), _PIP_EDITABLE_OPTS),
116
120
  "Installing `azure-cli-telemetry`..."
117
121
  )
118
122
  pip_cmd(
119
- "install -e {} --no-deps".format(os.path.join(cli_src, 'azure-cli-core')),
123
+ "install -e {} --no-deps {}".format(
124
+ quote_arg(os.path.join(cli_src, 'azure-cli-core')), _PIP_EDITABLE_OPTS),
120
125
  "Installing `azure-cli-core`..."
121
126
  )
122
127
 
123
128
  pip_cmd(
124
- "install -e {} --no-deps".format(os.path.join(cli_src, 'azure-cli')),
129
+ "install -e {} --no-deps {}".format(
130
+ quote_arg(os.path.join(cli_src, 'azure-cli')), _PIP_EDITABLE_OPTS),
125
131
  "Installing `azure-cli`..."
126
132
  )
127
133
 
@@ -129,14 +135,14 @@ def _install_cli(cli_path, deps=None):
129
135
  # azure-cli package for running commands.
130
136
  # Here we need to install with dependencies for azdev test.
131
137
  pip_cmd(
132
- "install -e {}".format(os.path.join(cli_src, 'azure-cli-testsdk')),
138
+ "install -e {} {}".format(quote_arg(os.path.join(cli_src, 'azure-cli-testsdk')), _PIP_EDITABLE_OPTS),
133
139
  "Installing `azure-cli-testsdk`..."
134
140
  )
135
141
  import platform
136
142
  system = platform.system()
137
143
  req_file = 'requirements.py3.{}.txt'.format(system)
138
144
  pip_cmd(
139
- "install -r {}".format(os.path.join(cli_src, 'azure-cli', req_file)),
145
+ "install -r {}".format(quote_arg(os.path.join(cli_src, 'azure-cli', req_file))),
140
146
  "Installing `{}`...".format(req_file)
141
147
  )
142
148
 
@@ -9,7 +9,7 @@ import sys
9
9
 
10
10
  from knack.log import get_logger
11
11
 
12
- from azdev.utilities import call
12
+ from azdev.utilities import call, quote_arg
13
13
 
14
14
 
15
15
  def get_test_runner(parallel, log_path, last_failed, no_exit_first, mark):
@@ -18,10 +18,11 @@ def get_test_runner(parallel, log_path, last_failed, no_exit_first, mark):
18
18
 
19
19
  logger = get_logger(__name__)
20
20
 
21
+ quoted_log_path = quote_arg(log_path)
21
22
  if os.name == 'posix':
22
- arguments = ['-x', '-v', '--forked', '-p no:warnings', '--log-level=WARN', '--junit-xml', log_path]
23
+ arguments = ['-x', '-v', '--forked', '-p no:warnings', '--log-level=WARN', '--junit-xml', quoted_log_path]
23
24
  else:
24
- arguments = ['-x', '-v', '-p no:warnings', '--log-level=WARN', '--junit-xml', log_path]
25
+ arguments = ['-x', '-v', '-p no:warnings', '--log-level=WARN', '--junit-xml', quoted_log_path]
25
26
 
26
27
  if no_exit_first:
27
28
  arguments.remove('-x')
@@ -29,7 +30,7 @@ def get_test_runner(parallel, log_path, last_failed, no_exit_first, mark):
29
30
  if mark:
30
31
  arguments.append('-m "{}"'.format(mark))
31
32
 
32
- arguments.extend(test_paths)
33
+ arguments.extend(quote_arg(test_path) for test_path in test_paths)
33
34
  if parallel:
34
35
  arguments += ['-n', 'auto']
35
36
  if last_failed:
@@ -15,6 +15,7 @@ from .command import (
15
15
  cmd,
16
16
  py_cmd,
17
17
  pip_cmd,
18
+ quote_arg,
18
19
  CommandError
19
20
  )
20
21
  from .const import (
@@ -71,6 +72,7 @@ __all__ = [
71
72
  'cmd',
72
73
  'py_cmd',
73
74
  'pip_cmd',
75
+ 'quote_arg',
74
76
  'CommandError',
75
77
  'test_cmd',
76
78
  'get_env_path',
@@ -15,6 +15,26 @@ from knack.util import CommandResultItem
15
15
  logger = get_logger(__name__)
16
16
 
17
17
 
18
+ def quote_arg(arg):
19
+ """ Quote a single command-line argument so it survives the shell/argv parsing
20
+ performed by the command runners in this module.
21
+
22
+ On Windows, command strings are passed verbatim to ``subprocess`` (and ultimately
23
+ ``CreateProcess``), so Windows-style quoting via ``subprocess.list2cmdline`` is used.
24
+ On POSIX, command strings are split with ``shlex.split``, so POSIX-style quoting via
25
+ ``shlex.quote`` is used. This makes paths containing spaces (e.g. OneDrive folders)
26
+ safe to interpolate into command strings.
27
+
28
+ :param arg: The argument to quote.
29
+ :returns: (str) the quoted argument.
30
+ """
31
+ from azdev.utilities import IS_WINDOWS
32
+ arg = str(arg)
33
+ if IS_WINDOWS:
34
+ return subprocess.list2cmdline([arg])
35
+ return shlex.quote(arg)
36
+
37
+
18
38
  class CommandError(Exception):
19
39
 
20
40
  def __init__(self, output, exit_code, command):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: azdev
3
- Version: 0.2.9
3
+ Version: 0.2.12
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
@@ -13,12 +13,12 @@ Classifier: Topic :: Software Development :: Build Tools
13
13
  Classifier: Environment :: Console
14
14
  Classifier: License :: OSI Approved :: MIT License
15
15
  Classifier: Natural Language :: English
16
- Classifier: Programming Language :: Python :: 3.9
17
16
  Classifier: Programming Language :: Python :: 3.10
18
17
  Classifier: Programming Language :: Python :: 3.11
19
18
  Classifier: Programming Language :: Python :: 3.12
20
19
  Classifier: Programming Language :: Python :: 3.13
21
- Requires-Python: >=3.9
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Requires-Python: >=3.10
22
22
  License-File: LICENSE
23
23
  Requires-Dist: azure-multiapi-storage
24
24
  Requires-Dist: docutils
@@ -26,7 +26,7 @@ Requires-Dist: flake8
26
26
  Requires-Dist: gitpython
27
27
  Requires-Dist: jinja2
28
28
  Requires-Dist: knack
29
- Requires-Dist: pylint<4
29
+ Requires-Dist: pylint<5,>=4
30
30
  Requires-Dist: pytest-xdist
31
31
  Requires-Dist: pytest-forked
32
32
  Requires-Dist: pytest>=5.0.0
@@ -38,9 +38,9 @@ Requires-Dist: jsbeautifier~=1.14.7
38
38
  Requires-Dist: deepdiff~=8.6.1
39
39
  Requires-Dist: azure-cli-diff-tool~=0.1.1
40
40
  Requires-Dist: packaging
41
+ Requires-Dist: pkginfo
41
42
  Requires-Dist: tqdm
42
- Requires-Dist: wheel==0.30.0
43
- Requires-Dist: setuptools==70.0.0
43
+ Requires-Dist: setuptools<80,>=78.1.1
44
44
  Requires-Dist: microsoft-security-utilities-secret-masker~=1.0.0b4
45
45
  Dynamic: author
46
46
  Dynamic: author-email
@@ -203,12 +203,37 @@ License
203
203
 
204
204
  Release History
205
205
  ===============
206
+ 0.2.12
207
+ ++++++
208
+ * Editable installs (``azdev setup``, ``azdev extension add``, code generation) now pass ``--no-build-isolation`` so wheels build against the environment's pinned ``setuptools`` instead of an isolated PEP 517 build environment. This fixes editable-install failures introduced by the ``setuptools`` pinning.
209
+
210
+ 0.2.12b3
211
+ ++++++++
212
+ * ``azdev extension update-index``/``publish``: Fix ``ValueError: Not a known wheel archive format`` when reading wheel metadata. The wheel is now downloaded with its ``.whl`` filename so ``pkginfo.Wheel`` can read it, and ``read_pkginfo`` falls back to a temporary ``.whl`` copy for any non-``.whl`` path. (#7740)
213
+
214
+ 0.2.12b2
215
+ ++++++++
216
+ * Quote paths when running editable installs (``pip install -e <path>``) and the pytest runner so that paths containing spaces (e.g. OneDrive folders) no longer break ``azdev extension add``, ``azdev setup``, code generation, and ``azdev test``. (#550, #415)
217
+
218
+ 0.2.12b1
219
+ ++++++++
220
+ * Extract extension metadata generation logic and decouple from ``wheel==0.30.0``; read wheel ``METADATA`` via ``pkginfo`` instead of the legacy ``metadata.json`` artifact. Drops the ``wheel==0.30.0`` and ``setuptools==70.0.0`` pins. (#521)
221
+
222
+ 0.2.11
223
+ ++++++
224
+ * `azdev extension add/remove`: Invalidate command index after installing or removing extensions.
225
+
226
+ 0.2.10
227
+ ++++++
228
+ * Add support for Python 3.14 and drop support for Python 3.9
229
+ * Bump pylint constraint to >=4,<5 for Python 3.14 and style compatibility
230
+
206
231
  0.2.9
207
232
  +++++
208
233
  * `azdev latest-index`: Add `generate` and `verify` commands to manage Azure CLI packaged latest indices (`commandIndex.latest.json`, `helpIndex.latest.json`) with CI-friendly verify exit behavior.
209
234
 
210
235
  0.2.8
211
- ++++++
236
+ +++++
212
237
  * Pin pip to 25.2 as pip 25.3 remove support for the legacy setup.py develop editable method in setuptools editable installs; setuptools >= 64 is now required. (#11457)
213
238
 
214
239
  0.2.7
@@ -67,6 +67,7 @@ azdev/operations/command_change/__init__.py
67
67
  azdev/operations/command_change/custom.py
68
68
  azdev/operations/command_change/util.py
69
69
  azdev/operations/extensions/__init__.py
70
+ azdev/operations/extensions/metadata.py
70
71
  azdev/operations/extensions/util.py
71
72
  azdev/operations/extensions/version_upgrade.py
72
73
  azdev/operations/help/__init__.py
@@ -4,7 +4,7 @@ flake8
4
4
  gitpython
5
5
  jinja2
6
6
  knack
7
- pylint<4
7
+ pylint<5,>=4
8
8
  pytest-xdist
9
9
  pytest-forked
10
10
  pytest>=5.0.0
@@ -16,7 +16,7 @@ jsbeautifier~=1.14.7
16
16
  deepdiff~=8.6.1
17
17
  azure-cli-diff-tool~=0.1.1
18
18
  packaging
19
+ pkginfo
19
20
  tqdm
20
- wheel==0.30.0
21
- setuptools==70.0.0
21
+ setuptools<80,>=78.1.1
22
22
  microsoft-security-utilities-secret-masker~=1.0.0b4
@@ -40,14 +40,14 @@ setup(
40
40
  'Environment :: Console',
41
41
  'License :: OSI Approved :: MIT License',
42
42
  'Natural Language :: English',
43
- 'Programming Language :: Python :: 3.9',
44
43
  'Programming Language :: Python :: 3.10',
45
44
  'Programming Language :: Python :: 3.11',
46
45
  'Programming Language :: Python :: 3.12',
47
- 'Programming Language :: Python :: 3.13'
46
+ 'Programming Language :: Python :: 3.13',
47
+ 'Programming Language :: Python :: 3.14'
48
48
  ],
49
49
  keywords='azure',
50
- python_requires='>=3.9',
50
+ python_requires='>=3.10',
51
51
  packages=[
52
52
  'azdev',
53
53
  'azdev.config',
@@ -73,7 +73,7 @@ setup(
73
73
  'gitpython',
74
74
  'jinja2',
75
75
  'knack',
76
- 'pylint<4',
76
+ 'pylint>=4,<5',
77
77
  'pytest-xdist', # depends on pytest-forked
78
78
  'pytest-forked',
79
79
  'pytest>=5.0.0',
@@ -85,9 +85,12 @@ setup(
85
85
  'deepdiff~=8.6.1',
86
86
  'azure-cli-diff-tool~=0.1.1',
87
87
  'packaging',
88
+ 'pkginfo',
88
89
  'tqdm',
89
- 'wheel==0.30.0',
90
- 'setuptools==70.0.0',
90
+ # setuptools >= 78.1.1 to address CVE-2025-47273 (path traversal).
91
+ # Upper bound < 80 keeps legacy `setup.py develop` invocations working
92
+ # for extensions that haven't migrated to pyproject.toml yet.
93
+ 'setuptools>=78.1.1,<80',
91
94
  'microsoft-security-utilities-secret-masker~=1.0.0b4'
92
95
  ],
93
96
  package_data={
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes