From bc488ff3623fc1ecbfff72bee4814836a3fc4172 Mon Sep 17 00:00:00 2001 From: Yang An Date: Mon, 4 May 2026 17:14:52 +1000 Subject: [PATCH 1/2] Support Python 3.14 in CI, packaging, and metadata --- .azure-pipelines/breaking-change-tests.yml | 4 +- .azure-pipelines/macos-standalone-release.yml | 2 +- .../templates/macos/macos-build-jobs.yml | 4 +- .../macos/macos-cask-generation-and-tests.yml | 2 +- .../templates/macos/macos-publish-jobs.yml | 2 +- .../macos/macos-sign-notarize-jobs.yml | 2 +- azure-pipelines-full-tests.yml | 6 +- azure-pipelines.yml | 74 +++++++++---------- doc/command_guidelines.md | 2 +- doc/extensions/authoring.md | 4 +- doc/install_linux_prerequisites.md | 2 +- scripts/regression_test/regression_test.yml | 8 +- scripts/release/debian/build.sh | 2 +- .../homebrew/docker/formula_generate.py | 2 +- scripts/release/macos/build_binary_tar_gz.py | 4 +- scripts/release/macos/cask_generate.py | 2 +- src/azure-cli-core/azure/cli/core/__init__.py | 49 ++++++++++++ src/azure-cli-core/setup.py | 1 + src/azure-cli-telemetry/setup.py | 1 + src/azure-cli-testsdk/setup.py | 1 + src/azure-cli/setup.py | 1 + 21 files changed, 114 insertions(+), 61 deletions(-) diff --git a/.azure-pipelines/breaking-change-tests.yml b/.azure-pipelines/breaking-change-tests.yml index 893140f6f64..92a6f178c0f 100644 --- a/.azure-pipelines/breaking-change-tests.yml +++ b/.azure-pipelines/breaking-change-tests.yml @@ -21,9 +21,9 @@ jobs: fetchDepth: 0 fetchTags: true - task: UsePythonVersion@0 - displayName: 'Use Python 3.13' + displayName: 'Use Python 3.14' inputs: - versionSpec: 3.13 + versionSpec: '3.14' - template: ${{ variables.Pipeline.Workspace }}/.azure-pipelines/templates/azdev_setup.yml - bash: | set -ev diff --git a/.azure-pipelines/macos-standalone-release.yml b/.azure-pipelines/macos-standalone-release.yml index f059e011188..31aee2ba575 100644 --- a/.azure-pipelines/macos-standalone-release.yml +++ b/.azure-pipelines/macos-standalone-release.yml @@ -20,7 +20,7 @@ parameters: - name: PythonVersion displayName: 'Python Version (Homebrew)' type: string - default: '3.13' + default: '3.14' # Sign/notarize parameters - name: BundleId diff --git a/.azure-pipelines/templates/macos/macos-build-jobs.yml b/.azure-pipelines/templates/macos/macos-build-jobs.yml index 1cf050eacb4..9387f309498 100644 --- a/.azure-pipelines/templates/macos/macos-build-jobs.yml +++ b/.azure-pipelines/templates/macos/macos-build-jobs.yml @@ -4,7 +4,7 @@ # Usage: Can be called from main pipeline or standalone wrapper # # Parameters: -# - PythonVersion: Homebrew Python version (default: 3.13) +# - PythonVersion: Homebrew Python version (default: 3.14) # - MacosArm64Image: VM image for ARM64 builds # - MacosIntelImage: VM image for Intel builds # - condition: Job execution condition @@ -17,7 +17,7 @@ parameters: - name: PythonVersion type: string - default: '3.13' + default: '3.14' - name: MacosArm64Image type: string default: 'macos-15-arm64' diff --git a/.azure-pipelines/templates/macos/macos-cask-generation-and-tests.yml b/.azure-pipelines/templates/macos/macos-cask-generation-and-tests.yml index cf4980a3dbc..5a29d6dce84 100644 --- a/.azure-pipelines/templates/macos/macos-cask-generation-and-tests.yml +++ b/.azure-pipelines/templates/macos/macos-cask-generation-and-tests.yml @@ -27,7 +27,7 @@ parameters: default: 'macos-15' - name: PythonVersion type: string - default: '3.13' + default: '3.14' - name: GitHubRepo type: string default: 'placeholder/repo' diff --git a/.azure-pipelines/templates/macos/macos-publish-jobs.yml b/.azure-pipelines/templates/macos/macos-publish-jobs.yml index 1eb769bac63..6640ef48283 100644 --- a/.azure-pipelines/templates/macos/macos-publish-jobs.yml +++ b/.azure-pipelines/templates/macos/macos-publish-jobs.yml @@ -46,7 +46,7 @@ parameters: default: 'macos-15' - name: PythonVersion type: string - default: '3.13' + default: '3.14' - name: Debug type: boolean default: false diff --git a/.azure-pipelines/templates/macos/macos-sign-notarize-jobs.yml b/.azure-pipelines/templates/macos/macos-sign-notarize-jobs.yml index 59e6a8006d6..657a4f50500 100644 --- a/.azure-pipelines/templates/macos/macos-sign-notarize-jobs.yml +++ b/.azure-pipelines/templates/macos/macos-sign-notarize-jobs.yml @@ -33,7 +33,7 @@ parameters: default: [] - name: PythonVersion type: string - default: '3.13' + default: '3.14' - name: MacosArm64Image type: string default: 'macos-15-arm64' diff --git a/azure-pipelines-full-tests.yml b/azure-pipelines-full-tests.yml index 506c5c10ff9..8015a713518 100644 --- a/azure-pipelines-full-tests.yml +++ b/azure-pipelines-full-tests.yml @@ -50,8 +50,8 @@ jobs: fullTest: true jobName: 'FullTest' -- job: AutomationFullTestPython313ProfileLatest - displayName: Automation Full Test Python313 Profile Latest +- job: AutomationFullTestPython314ProfileLatest + displayName: Automation Full Test Python314 Profile Latest timeoutInMinutes: 9999 strategy: maxParallel: 8 @@ -77,7 +77,7 @@ jobs: steps: - template: .azure-pipelines/templates/automation_test.yml parameters: - pythonVersion: '3.13' + pythonVersion: '3.14' profile: 'latest' instance_cnt: '8' instance_idx: '$(Instance_idx)' diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4605adf7cf7..1edcdc015fe 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -25,7 +25,7 @@ variables: # macOS Cask parameters - name: macos_cask_python_version - value: '3.13' + value: '3.14' parameters: - name: architectures @@ -137,9 +137,9 @@ jobs: steps: - task: UsePythonVersion@0 - displayName: 'Use Python 3.13' + displayName: 'Use Python 3.14' inputs: - versionSpec: 3.13 + versionSpec: '3.14' - bash: ./scripts/ci/dependency_check.sh displayName: 'Verify src/azure-cli/requirements.py3.Linux.txt' @@ -152,9 +152,9 @@ jobs: steps: - task: UsePythonVersion@0 - displayName: 'Use Python 3.13' + displayName: 'Use Python 3.14' inputs: - versionSpec: 3.13 + versionSpec: '3.14' - bash: ./scripts/ci/dependency_check.sh displayName: 'Verify src/azure-cli/requirements.py3.Darwin.txt' @@ -167,9 +167,9 @@ jobs: steps: - task: UsePythonVersion@0 - displayName: 'Use Python 3.13' + displayName: 'Use Python 3.14' inputs: - versionSpec: 3.13 + versionSpec: '3.14' - task: BatchScript@1 inputs: @@ -184,9 +184,9 @@ jobs: name: ${{ variables.ubuntu_pool }} steps: - task: UsePythonVersion@0 - displayName: 'Use Python 3.13' + displayName: 'Use Python 3.14' inputs: - versionSpec: 3.13 + versionSpec: '3.14' - template: .azure-pipelines/templates/azdev_setup.yml - bash: | set -ev @@ -439,9 +439,9 @@ jobs: name: ${{ variables.ubuntu_pool }} steps: - task: UsePythonVersion@0 - displayName: 'Use Python 3.13' + displayName: 'Use Python 3.14' inputs: - versionSpec: 3.13 + versionSpec: '3.14' - task: PipAuthenticate@1 condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/release') @@ -476,8 +476,8 @@ jobs: matrix: Python312: python.version: '3.12' - Python313: - python.version: '3.13' + Python314: + python.version: '3.14' dependsOn: BuildPythonWheel condition: succeeded() pool: @@ -516,8 +516,8 @@ jobs: matrix: Python312: python.version: '3.12' - Python313: - python.version: '3.13' + Python314: + python.version: '3.14' steps: - template: .azure-pipelines/templates/automation_test.yml parameters: @@ -533,8 +533,8 @@ jobs: matrix: Python312: python.version: '3.12' - Python313: - python.version: '3.13' + Python314: + python.version: '3.14' steps: - template: .azure-pipelines/templates/automation_test.yml parameters: @@ -553,8 +553,8 @@ jobs: matrix: Python312: python.version: '3.12' - Python313: - python.version: '3.13' + Python314: + python.version: '3.14' steps: - task: UsePythonVersion@0 displayName: 'Use Python $(python.version)' @@ -574,8 +574,8 @@ jobs: name: ${{ variables.ubuntu_pool }} strategy: matrix: - Python313: - python.version: '3.13' + Python314: + python.version: '3.14' steps: - task: UsePythonVersion@0 displayName: 'Use Python $(python.version)' @@ -667,7 +667,7 @@ jobs: set -ev # Force relink python@3.xx in Homebrew to resolve the conflict with pre-installed python 3.xx on macOS-12 image # See: https://github.com/Azure/azure-cli/issues/29054 - python_version=3.13 + python_version=3.14 brew unlink python@$python_version && brew link --overwrite python@$python_version echo == Remove pre-installed azure-cli == @@ -1087,9 +1087,9 @@ jobs: name: ${{ variables.ubuntu_pool }} steps: - task: UsePythonVersion@0 - displayName: 'Use Python 3.13' + displayName: 'Use Python 3.14' inputs: - versionSpec: 3.13 + versionSpec: '3.14' - template: .azure-pipelines/templates/azdev_setup.yml - bash: | set -ev @@ -1104,9 +1104,9 @@ jobs: name: ${{ variables.ubuntu_multi_core_pool }} steps: - task: UsePythonVersion@0 - displayName: 'Use Python 3.13' + displayName: 'Use Python 3.14' inputs: - versionSpec: 3.13 + versionSpec: '3.14' - template: .azure-pipelines/templates/azdev_setup.yml - bash: | set -ev @@ -1120,9 +1120,9 @@ jobs: name: ${{ variables.ubuntu_pool }} steps: - task: UsePythonVersion@0 - displayName: 'Use Python 3.13' + displayName: 'Use Python 3.14' inputs: - versionSpec: 3.13 + versionSpec: '3.14' - template: .azure-pipelines/templates/azdev_setup.yml - bash: | set -ev @@ -1137,8 +1137,8 @@ jobs: matrix: Python312: python.version: '3.12' - Python313: - python.version: '3.13' + Python314: + python.version: '3.14' pool: name: ${{ variables.ubuntu_pool }} steps: @@ -1166,9 +1166,9 @@ jobs: name: ${{ variables.ubuntu_pool }} steps: - task: UsePythonVersion@0 - displayName: 'Use Python 3.13' + displayName: 'Use Python 3.14' inputs: - versionSpec: 3.13 + versionSpec: 3.14 - bash: | #!/usr/bin/env bash set -ev @@ -1184,9 +1184,9 @@ jobs: name: ${{ variables.ubuntu_multi_core_pool }} steps: - task: UsePythonVersion@0 - displayName: 'Use Python 3.13' + displayName: 'Use Python 3.14' inputs: - versionSpec: 3.13 + versionSpec: '3.14' - template: .azure-pipelines/templates/azdev_setup.yml - bash: | set -ev @@ -1207,9 +1207,9 @@ jobs: name: ${{ variables.ubuntu_pool }} steps: - task: UsePythonVersion@0 - displayName: 'Use Python 3.13' + displayName: 'Use Python 3.14' inputs: - versionSpec: 3.13 + versionSpec: '3.14' - template: .azure-pipelines/templates/azdev_setup.yml - bash: | set -ev @@ -1242,7 +1242,7 @@ jobs: - task: UsePythonVersion@0 displayName: 'Use Python 3.11' inputs: - versionSpec: 3.11 + versionSpec: '3.11' - template: .azure-pipelines/templates/azdev_setup.yml - bash: | set -ev diff --git a/doc/command_guidelines.md b/doc/command_guidelines.md index c79fe9732d4..75059f0ff48 100644 --- a/doc/command_guidelines.md +++ b/doc/command_guidelines.md @@ -426,7 +426,7 @@ Follow the [Error Handling Guidelines](https://github.com/Azure/azure-cli/blob/d ## Coding Practices -- All code must support Python 3.10 ~ 3.13 +- All code must support Python 3.10 ~ 3.14 - PRs to Azure/azure-cli and Azure/azure-cli-extensions must pass CI - Code must pass style checks with pylint and pep8 - (*) All commands should have tests diff --git a/doc/extensions/authoring.md b/doc/extensions/authoring.md index 9ccac43dee4..2258e70bda1 100644 --- a/doc/extensions/authoring.md +++ b/doc/extensions/authoring.md @@ -130,9 +130,9 @@ See [Extension Metadata](metadata.md) for more information. ### Test your extension on Python 3 -- The Azure CLI supports Python 3.10 ~ 3.13 so verify that your extension does the same. +- The Azure CLI supports Python 3.10 ~ 3.14 so verify that your extension does the same. - You can create virtual environments for different versions and run your extension in them. -- e.g. `python3.13 -m venv env313`. +- e.g. `python3.14 -m venv env314`. Also, see the [FAQ](faq.md). diff --git a/doc/install_linux_prerequisites.md b/doc/install_linux_prerequisites.md index 7166c9664ef..5b006179282 100644 --- a/doc/install_linux_prerequisites.md +++ b/doc/install_linux_prerequisites.md @@ -6,7 +6,7 @@ Some native Linux packages are required when installing the CLI with: - Interactive installation script - `pip` -Current supported Python versions are Python 3.10 ~ 3.13. +Current supported Python versions are Python 3.10 ~ 3.14. The commands to run to install the dependencies for some common distributions are listed below. diff --git a/scripts/regression_test/regression_test.yml b/scripts/regression_test/regression_test.yml index 9f6cdef4277..35f6dcafb34 100644 --- a/scripts/regression_test/regression_test.yml +++ b/scripts/regression_test/regression_test.yml @@ -20,8 +20,8 @@ jobs: steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.13' - displayName: "Use Python 3.13" + versionSpec: '3.14' + displayName: "Use Python 3.14" - task: AzureCLI@2 displayName: 'update version' inputs: @@ -89,8 +89,8 @@ jobs: steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.13' - displayName: "Use Python 3.13" + versionSpec: '3.14' + displayName: "Use Python 3.14" - task: AzureCLI@2 displayName: 'Checkout Target Branch' inputs: diff --git a/scripts/release/debian/build.sh b/scripts/release/debian/build.sh index 6ff45453ddf..4588bc91735 100755 --- a/scripts/release/debian/build.sh +++ b/scripts/release/debian/build.sh @@ -15,7 +15,7 @@ set -exv ls -Rl /mnt/artifacts WORKDIR=`cd $(dirname $0); cd ../../../; pwd` -PYTHON_VERSION="3.13.13" +PYTHON_VERSION="3.14.4" SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # Update APT packages diff --git a/scripts/release/homebrew/docker/formula_generate.py b/scripts/release/homebrew/docker/formula_generate.py index 6e27c95ce72..b888921abf5 100644 --- a/scripts/release/homebrew/docker/formula_generate.py +++ b/scripts/release/homebrew/docker/formula_generate.py @@ -19,7 +19,7 @@ CLI_VERSION = os.environ['CLI_VERSION'] HOMEBREW_UPSTREAM_URL = os.environ['HOMEBREW_UPSTREAM_URL'] HOMEBREW_FORMULAR_LATEST = "https://raw.githubusercontent.com/Homebrew/homebrew-core/master/Formula/a/azure-cli.rb" -PYTHON_VERSION = '3.13' +PYTHON_VERSION = '3.14' def main(): diff --git a/scripts/release/macos/build_binary_tar_gz.py b/scripts/release/macos/build_binary_tar_gz.py index 0215d254217..957eb626f2b 100644 --- a/scripts/release/macos/build_binary_tar_gz.py +++ b/scripts/release/macos/build_binary_tar_gz.py @@ -38,7 +38,7 @@ ├── bin/ │ └── az (entry script - Homebrew or AZ_PYTHON) └── lib/ - └── python3.13 + └── python3.14 └── site-packages/ ├── azure/ ├── msal/ @@ -81,7 +81,7 @@ # Python version we're building for (must match Homebrew python@X.Y) # Can be overridden via PYTHON_MAJOR_MINOR env var -PYTHON_MAJOR_MINOR = os.environ.get("PYTHON_MAJOR_MINOR", "3.13") +PYTHON_MAJOR_MINOR = os.environ.get("PYTHON_MAJOR_MINOR", "3.14") PYTHON_BIN = "python3" TEMPLATE_DIR = Path(__file__).resolve().parent / "templates" LAUNCHER_TEMPLATE_PATH = TEMPLATE_DIR / "az_launcher.sh.in" diff --git a/scripts/release/macos/cask_generate.py b/scripts/release/macos/cask_generate.py index e827cd3f474..6520ad4612f 100644 --- a/scripts/release/macos/cask_generate.py +++ b/scripts/release/macos/cask_generate.py @@ -77,7 +77,7 @@ def main() -> None: parser.add_argument("--arm64-sha", dest="arm64_sha", help="ARM64 tarball SHA256") parser.add_argument("--x86-64-sha", dest="x86_64_sha", help="x86_64 tarball SHA256") parser.add_argument("--github-repo", dest="github_repo", help="GitHub repo, e.g. Azure/azure-cli") - parser.add_argument("--python-version", dest="python_version", help="Python major.minor version, e.g. 3.13") + parser.add_argument("--python-version", dest="python_version", help="Python major.minor version, e.g. 3.14") parser.add_argument("--template", dest="template", help="Template path (.rb.in)") parser.add_argument("--output", dest="output", help="Output cask path (.rb)") parser.set_defaults(func=generate_cask) diff --git a/src/azure-cli-core/azure/cli/core/__init__.py b/src/azure-cli-core/azure/cli/core/__init__.py index ccbdce87f75..1a24ecaf351 100644 --- a/src/azure-cli-core/azure/cli/core/__init__.py +++ b/src/azure-cli-core/azure/cli/core/__init__.py @@ -42,6 +42,45 @@ # Maximum number of worker threads for parallel module loading. MAX_WORKER_THREAD_COUNT = 4 +# Shared Azure SDK trunks that many command modules import independently. Pre-warming +# these on the main thread before parallel module loading prevents +# importlib._DeadlockError on Python 3.14+ (which raises on concurrent imports of +# the same module instead of blocking, as 3.13 and earlier did). Order matters: +# import base packages before their subpackages so each lock is taken once. +_SHARED_PREWARM_MODULES = ( + 'azure.core', + 'azure.core.exceptions', + 'azure.core.pipeline', + 'azure.core.pipeline.policies', + 'azure.mgmt.core', + 'msrest', + 'msrest.serialization', + 'msrest.http_logger', + 'msrestazure', + 'msrestazure.azure_exceptions', +) +_prewarm_done = False + + +def _prewarm_shared_imports(): + """Import shared Azure SDK trunks once on the main thread. + + See `_SHARED_PREWARM_MODULES` for rationale. Safe to call repeatedly; a flag + guards against repeated work. Missing optional modules are ignored so this + helper never blocks startup if an SDK package is uninstalled. + """ + global _prewarm_done # pylint: disable=global-statement + if _prewarm_done: + return + from importlib import import_module + for name in _SHARED_PREWARM_MODULES: + try: + import_module(name) + except ImportError: + # Optional/transitive SDK module not present in this install. + pass + _prewarm_done = True + def _get_top_level_command(args): """Return normalized top-level command token or None when unavailable.""" @@ -669,6 +708,16 @@ def _load_modules(self, args, command_modules): from concurrent.futures import ThreadPoolExecutor from azure.cli.core.commands import BLOCKED_MODS + # Pre-warm shared Azure SDK trunks on the main thread before fanning out to + # worker threads. On Python 3.14+, importlib raises _DeadlockError instead + # of blocking when two threads concurrently import the same module + # (https://docs.python.org/3.14/whatsnew/3.14.html). Many command modules + # independently `import azure.core` / `import msrest` etc., which races + # under the thread pool. By importing these once on the main thread first, + # worker threads see fully-initialized entries in sys.modules and never + # contend on the module lock. + _prewarm_shared_imports() + results = [] with ThreadPoolExecutor(max_workers=MAX_WORKER_THREAD_COUNT) as executor: future_to_module = {executor.submit(self._load_single_module, mod, args): mod diff --git a/src/azure-cli-core/setup.py b/src/azure-cli-core/setup.py index 8dce90c7c77..7a775aa6737 100644 --- a/src/azure-cli-core/setup.py +++ b/src/azure-cli-core/setup.py @@ -39,6 +39,7 @@ 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: 3.14', 'License :: OSI Approved :: MIT License', ] diff --git a/src/azure-cli-telemetry/setup.py b/src/azure-cli-telemetry/setup.py index adb5198204b..63a6b21cc61 100755 --- a/src/azure-cli-telemetry/setup.py +++ b/src/azure-cli-telemetry/setup.py @@ -20,6 +20,7 @@ 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: 3.14', 'License :: OSI Approved :: MIT License', ] diff --git a/src/azure-cli-testsdk/setup.py b/src/azure-cli-testsdk/setup.py index 55f63b2d191..d32fe0bebcc 100644 --- a/src/azure-cli-testsdk/setup.py +++ b/src/azure-cli-testsdk/setup.py @@ -19,6 +19,7 @@ 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: 3.14', 'License :: OSI Approved :: MIT License', ] diff --git a/src/azure-cli/setup.py b/src/azure-cli/setup.py index 756cf19c44e..004accb9d64 100644 --- a/src/azure-cli/setup.py +++ b/src/azure-cli/setup.py @@ -46,6 +46,7 @@ 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: 3.14', 'License :: OSI Approved :: MIT License', ] From f23ec3b7fa7c9f6068fa73e834a1955db1fb286a Mon Sep 17 00:00:00 2001 From: YangAn-microsoft Date: Mon, 18 May 2026 16:02:20 +1000 Subject: [PATCH 2/2] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/azure-cli-core/azure/cli/core/__init__.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/__init__.py b/src/azure-cli-core/azure/cli/core/__init__.py index 1a24ecaf351..07d700d061e 100644 --- a/src/azure-cli-core/azure/cli/core/__init__.py +++ b/src/azure-cli-core/azure/cli/core/__init__.py @@ -76,9 +76,12 @@ def _prewarm_shared_imports(): for name in _SHARED_PREWARM_MODULES: try: import_module(name) - except ImportError: - # Optional/transitive SDK module not present in this install. - pass + except ModuleNotFoundError as ex: + missing_name = getattr(ex, 'name', None) + if missing_name == name or (missing_name and name.startswith(missing_name + '.')): + # Optional/transitive SDK module not present in this install. + continue + raise _prewarm_done = True