From 50ea9d9d38550c4d22e6374c21407dced4225298 Mon Sep 17 00:00:00 2001 From: l0lawrence Date: Thu, 22 Oct 2026 10:12:43 -0700 Subject: [PATCH 1/6] fix(http-client-python): upgrade installer tooling with security minimums Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../python-tooling-minimums-2026-10-22.md | 7 +++++ .github/workflows/ci-python.yml | 18 +++++++++++++ .../eng/scripts/setup/package_manager.py | 26 +++++++++++++++---- .../tests/install_packages.py | 7 +++++ .../tests/requirements/base.txt | 3 +++ .../tests/requirements/docs.txt | 2 +- 6 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 .chronus/changes/python-tooling-minimums-2026-10-22.md diff --git a/.chronus/changes/python-tooling-minimums-2026-10-22.md b/.chronus/changes/python-tooling-minimums-2026-10-22.md new file mode 100644 index 00000000000..02cbb08c498 --- /dev/null +++ b/.chronus/changes/python-tooling-minimums-2026-10-22.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/http-client-python" +--- + +Upgrade pip and uv in generator virtual environments to compatible releases satisfying pip >=26.2.2 and uv >=0.13.4. Require certifi >=2026.10.19 for test clients using the requests transport. diff --git a/.github/workflows/ci-python.yml b/.github/workflows/ci-python.yml index 1b0fe755b01..7870fb505e1 100644 --- a/.github/workflows/ci-python.yml +++ b/.github/workflows/ci-python.yml @@ -22,6 +22,9 @@ jobs: - name: Install repo dependencies run: pnpm install + - name: Upgrade Python installers + run: python -m pip install --upgrade "pip>=26.2.2" "uv>=0.13.4" + - name: Setup http-client-python # Avoid npm lifecycle side effects and lockfile rewrites in CI; setup still runs build + generator install. run: | @@ -82,6 +85,9 @@ jobs: - name: Install repo dependencies run: pnpm install + - name: Upgrade Python installers + run: python -m pip install --upgrade "pip>=26.2.2" "uv>=0.13.4" + - name: Setup http-client-python # Generated artifacts come from the build job; downstream jobs only need the Python package installed and test tools prepared. run: | @@ -116,6 +122,9 @@ jobs: - name: Install repo dependencies run: pnpm install + - name: Upgrade Python installers + run: python -m pip install --upgrade "pip>=26.2.2" "uv>=0.13.4" + - name: Setup http-client-python # Generated artifacts come from the build job; downstream jobs only need the Python package installed and test tools prepared. run: | @@ -150,6 +159,9 @@ jobs: - name: Install repo dependencies run: pnpm install + - name: Upgrade Python installers + run: python -m pip install --upgrade "pip>=26.2.2" "uv>=0.13.4" + - name: Setup http-client-python # Generated artifacts come from the build job; downstream jobs only need the Python package installed and test tools prepared. run: | @@ -195,6 +207,9 @@ jobs: - name: Install repo dependencies run: pnpm install + - name: Upgrade Python installers + run: python -m pip install --upgrade "pip>=26.2.2" "uv>=0.13.4" + - name: Setup http-client-python # Generated artifacts come from the build job; downstream jobs only need the Python package installed and test tools prepared. run: | @@ -225,6 +240,9 @@ jobs: - name: Install repo dependencies run: pnpm install + - name: Upgrade Python installers + run: python -m pip install --upgrade "pip>=26.2.2" "uv>=0.13.4" + - name: Setup http-client-python # Windows validates build/regenerate/test in one job, so run the full setup instead of restoring Linux artifacts. run: | diff --git a/packages/http-client-python/eng/scripts/setup/package_manager.py b/packages/http-client-python/eng/scripts/setup/package_manager.py index 5725bddab35..c4054a0b97e 100644 --- a/packages/http-client-python/eng/scripts/setup/package_manager.py +++ b/packages/http-client-python/eng/scripts/setup/package_manager.py @@ -13,6 +13,8 @@ from pathlib import Path from venvtools import ExtendedEnvBuilder +TOOL_REQUIREMENTS = ["pip>=26.2.2", "uv>=0.13.4"] + class PackageManagerNotFoundError(Exception): """Raised when no suitable package manager is found.""" @@ -117,6 +119,16 @@ def install_packages(packages: list, venv_context=None, package_manager: str = N raise RuntimeError(f"Failed to install packages with {package_manager}: {e}") +def upgrade_tooling(venv_context, package_manager: str) -> None: + """Refresh tooling only in the supplied emitter-owned virtual environment.""" + host_install = get_install_command(package_manager) + if package_manager == "uv": + command = host_install + ["--python", venv_context.env_exe, "--upgrade", "--prerelease=disallow"] + else: + command = host_install[:-1] + ["--python", venv_context.env_exe, "install", "--upgrade"] + subprocess.run(command + TOOL_REQUIREMENTS, check=True) + + def create_venv_with_package_manager(venv_path): """Create virtual environment using the best available package manager. @@ -142,13 +154,17 @@ def __init__(self, venv_path): else str(venv_path / "Scripts" / "python.exe") ) - return MockVenvContext(venv_path) + context = MockVenvContext(venv_path) + upgrade_tooling(context, package_manager) + return context else: # Use standard venv for pip if venv_path.exists(): - env_builder = venv.EnvBuilder(with_pip=True) - return env_builder.ensure_directories(venv_path) + env_builder = venv.EnvBuilder(with_pip=False) + context = env_builder.ensure_directories(venv_path) else: - env_builder = ExtendedEnvBuilder(with_pip=True, upgrade_deps=True) + env_builder = ExtendedEnvBuilder(with_pip=False) env_builder.create(venv_path) - return env_builder.context + context = env_builder.context + upgrade_tooling(context, package_manager) + return context diff --git a/packages/http-client-python/tests/install_packages.py b/packages/http-client-python/tests/install_packages.py index ae7862e33fd..87ff0895db9 100644 --- a/packages/http-client-python/tests/install_packages.py +++ b/packages/http-client-python/tests/install_packages.py @@ -13,6 +13,11 @@ import os import subprocess import sys +from pathlib import Path +from types import SimpleNamespace + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "eng" / "scripts" / "setup")) +from package_manager import detect_package_manager, upgrade_tooling def _find_packages(generated_dir): @@ -75,6 +80,8 @@ def install_packages(flavor, tests_dir): print(f"Installing {len(packages)} packages from {generated_dir}") use_wheels = os.path.isdir(wheel_dir) and bool(glob.glob(os.path.join(wheel_dir, "*.whl"))) + if sys.prefix != sys.base_prefix: + upgrade_tooling(SimpleNamespace(env_exe=sys.executable), detect_package_manager()) use_uv = True if use_wheels: diff --git a/packages/http-client-python/tests/requirements/base.txt b/packages/http-client-python/tests/requirements/base.txt index bd55d8c0e26..bf1a34aba3c 100644 --- a/packages/http-client-python/tests/requirements/base.txt +++ b/packages/http-client-python/tests/requirements/base.txt @@ -9,4 +9,7 @@ isodate>=0.6.1 typing-extensions>=4.6.0 tox>=4.16.0 tox-uv>=1.0.0 +pip>=26.2.2; python_version >= "3.10" +uv>=0.13.4 +certifi>=2026.10.19 filelock>=3.12.0 diff --git a/packages/http-client-python/tests/requirements/docs.txt b/packages/http-client-python/tests/requirements/docs.txt index 5b80499ba34..cf8a59b399b 100644 --- a/packages/http-client-python/tests/requirements/docs.txt +++ b/packages/http-client-python/tests/requirements/docs.txt @@ -1,6 +1,6 @@ # Documentation dependencies -r base.txt -pip +pip>=26.2.2 pylint pkginfo sphinx>=7.0.0 From fe0e49a8a82ad1e1ad2b51119514baa408100667 Mon Sep 17 00:00:00 2001 From: l0lawrence Date: Thu, 22 Oct 2026 10:19:05 -0700 Subject: [PATCH 2/6] fix(http-client-python): avoid adding new test requirements Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .chronus/changes/python-tooling-minimums-2026-10-22.md | 2 +- packages/http-client-python/tests/requirements/base.txt | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.chronus/changes/python-tooling-minimums-2026-10-22.md b/.chronus/changes/python-tooling-minimums-2026-10-22.md index 02cbb08c498..b19e775663d 100644 --- a/.chronus/changes/python-tooling-minimums-2026-10-22.md +++ b/.chronus/changes/python-tooling-minimums-2026-10-22.md @@ -4,4 +4,4 @@ packages: - "@typespec/http-client-python" --- -Upgrade pip and uv in generator virtual environments to compatible releases satisfying pip >=26.2.2 and uv >=0.13.4. Require certifi >=2026.10.19 for test clients using the requests transport. +Upgrade pip and uv in generator virtual environments to compatible releases satisfying pip >=26.2.2 and uv >=0.13.4. diff --git a/packages/http-client-python/tests/requirements/base.txt b/packages/http-client-python/tests/requirements/base.txt index bf1a34aba3c..bd55d8c0e26 100644 --- a/packages/http-client-python/tests/requirements/base.txt +++ b/packages/http-client-python/tests/requirements/base.txt @@ -9,7 +9,4 @@ isodate>=0.6.1 typing-extensions>=4.6.0 tox>=4.16.0 tox-uv>=1.0.0 -pip>=26.2.2; python_version >= "3.10" -uv>=0.13.4 -certifi>=2026.10.19 filelock>=3.12.0 From 7bc121a56da2ae54771c04b9b84e3ec4b1a44f35 Mon Sep 17 00:00:00 2001 From: l0lawrence Date: Thu, 22 Oct 2026 10:58:28 -0700 Subject: [PATCH 3/6] fix(http-client-python): build wheels with pip and use venv uv Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../eng/scripts/setup/package_manager.py | 2 +- .../tests/install_packages.py | 19 +++++++------------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/packages/http-client-python/eng/scripts/setup/package_manager.py b/packages/http-client-python/eng/scripts/setup/package_manager.py index c4054a0b97e..368cc5a4293 100644 --- a/packages/http-client-python/eng/scripts/setup/package_manager.py +++ b/packages/http-client-python/eng/scripts/setup/package_manager.py @@ -69,7 +69,7 @@ def get_install_command(package_manager: str, venv_context=None) -> list: list: The base install command as a list """ if package_manager == "uv": - cmd = ["uv", "pip", "install"] + cmd = [venv_context.env_exe, "-m", "uv", "pip", "install"] if venv_context else ["uv", "pip", "install"] if venv_context: cmd.extend(["--python", venv_context.env_exe]) return cmd diff --git a/packages/http-client-python/tests/install_packages.py b/packages/http-client-python/tests/install_packages.py index 87ff0895db9..b13c1c7bdb9 100644 --- a/packages/http-client-python/tests/install_packages.py +++ b/packages/http-client-python/tests/install_packages.py @@ -48,16 +48,10 @@ def build_wheels(flavor, tests_dir): batch_size = 50 for i in range(0, len(packages), batch_size): batch = packages[i:i + batch_size] - try: - subprocess.run( - ["uv", "pip", "wheel", "--no-deps", "--wheel-dir", wheel_dir] + batch, - check=True, - ) - except FileNotFoundError: - subprocess.run( - [sys.executable, "-m", "pip", "wheel", "--no-deps", "--wheel-dir", wheel_dir] + batch, - check=True, - ) + subprocess.run( + [sys.executable, "-m", "pip", "wheel", "--no-deps", "--wheel-dir", wheel_dir] + batch, + check=True, + ) wheel_count = len(glob.glob(os.path.join(wheel_dir, "*.whl"))) print(f"Built {wheel_count} wheels for {flavor}") @@ -82,6 +76,7 @@ def install_packages(flavor, tests_dir): use_wheels = os.path.isdir(wheel_dir) and bool(glob.glob(os.path.join(wheel_dir, "*.whl"))) if sys.prefix != sys.base_prefix: upgrade_tooling(SimpleNamespace(env_exe=sys.executable), detect_package_manager()) + uv_command = [sys.executable, "-m", "uv"] if sys.prefix != sys.base_prefix else ["uv"] use_uv = True if use_wheels: @@ -91,7 +86,7 @@ def install_packages(flavor, tests_dir): wheel_files = glob.glob(os.path.join(wheel_dir, "*.whl")) print(f" Using {len(wheel_files)} pre-built wheels from .wheels/{flavor}/") try: - cmd = ["uv", "pip", "install", "--no-deps", "--no-index", "--find-links", wheel_dir] + wheel_files + cmd = uv_command + ["pip", "install", "--no-deps", "--no-index", "--find-links", wheel_dir] + wheel_files subprocess.run(cmd, check=True) print(f"Successfully installed {len(wheel_files)} packages") return @@ -113,7 +108,7 @@ def install_packages(flavor, tests_dir): for i in range(0, len(packages), batch_size): batch = packages[i:i + batch_size] if use_uv: - cmd = ["uv", "pip", "install", "--no-deps", "--cache-dir", cache_dir] + batch + cmd = uv_command + ["pip", "install", "--no-deps", "--cache-dir", cache_dir] + batch else: cmd = [sys.executable, "-m", "pip", "install", "--no-deps"] + batch try: From a66c8fd42488866f47eae54fff3ea85c77caf4f0 Mon Sep 17 00:00:00 2001 From: l0lawrence Date: Thu, 22 Oct 2026 14:20:44 -0700 Subject: [PATCH 4/6] fix(http-client-python): simplify virtual environment pip upgrades Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../python-tooling-minimums-2026-10-22.md | 2 +- .../eng/scripts/setup/package_manager.py | 22 +++++-------------- .../tests/install_packages.py | 12 ++-------- 3 files changed, 8 insertions(+), 28 deletions(-) diff --git a/.chronus/changes/python-tooling-minimums-2026-10-22.md b/.chronus/changes/python-tooling-minimums-2026-10-22.md index b19e775663d..d26180736f7 100644 --- a/.chronus/changes/python-tooling-minimums-2026-10-22.md +++ b/.chronus/changes/python-tooling-minimums-2026-10-22.md @@ -4,4 +4,4 @@ packages: - "@typespec/http-client-python" --- -Upgrade pip and uv in generator virtual environments to compatible releases satisfying pip >=26.2.2 and uv >=0.13.4. +Upgrade pip in existing generator virtual environments as well as newly created ones. diff --git a/packages/http-client-python/eng/scripts/setup/package_manager.py b/packages/http-client-python/eng/scripts/setup/package_manager.py index 368cc5a4293..61aa68254d8 100644 --- a/packages/http-client-python/eng/scripts/setup/package_manager.py +++ b/packages/http-client-python/eng/scripts/setup/package_manager.py @@ -13,8 +13,6 @@ from pathlib import Path from venvtools import ExtendedEnvBuilder -TOOL_REQUIREMENTS = ["pip>=26.2.2", "uv>=0.13.4"] - class PackageManagerNotFoundError(Exception): """Raised when no suitable package manager is found.""" @@ -69,7 +67,7 @@ def get_install_command(package_manager: str, venv_context=None) -> list: list: The base install command as a list """ if package_manager == "uv": - cmd = [venv_context.env_exe, "-m", "uv", "pip", "install"] if venv_context else ["uv", "pip", "install"] + cmd = ["uv", "pip", "install"] if venv_context: cmd.extend(["--python", venv_context.env_exe]) return cmd @@ -119,16 +117,6 @@ def install_packages(packages: list, venv_context=None, package_manager: str = N raise RuntimeError(f"Failed to install packages with {package_manager}: {e}") -def upgrade_tooling(venv_context, package_manager: str) -> None: - """Refresh tooling only in the supplied emitter-owned virtual environment.""" - host_install = get_install_command(package_manager) - if package_manager == "uv": - command = host_install + ["--python", venv_context.env_exe, "--upgrade", "--prerelease=disallow"] - else: - command = host_install[:-1] + ["--python", venv_context.env_exe, "install", "--upgrade"] - subprocess.run(command + TOOL_REQUIREMENTS, check=True) - - def create_venv_with_package_manager(venv_path): """Create virtual environment using the best available package manager. @@ -155,16 +143,16 @@ def __init__(self, venv_path): ) context = MockVenvContext(venv_path) - upgrade_tooling(context, package_manager) + install_packages(["--upgrade", "pip"], context, package_manager="uv") return context else: # Use standard venv for pip if venv_path.exists(): - env_builder = venv.EnvBuilder(with_pip=False) + env_builder = venv.EnvBuilder(with_pip=True) context = env_builder.ensure_directories(venv_path) + install_packages(["--upgrade", "pip"], context, package_manager) else: - env_builder = ExtendedEnvBuilder(with_pip=False) + env_builder = ExtendedEnvBuilder(with_pip=True, upgrade_deps=True) env_builder.create(venv_path) context = env_builder.context - upgrade_tooling(context, package_manager) return context diff --git a/packages/http-client-python/tests/install_packages.py b/packages/http-client-python/tests/install_packages.py index b13c1c7bdb9..2dfabb5542d 100644 --- a/packages/http-client-python/tests/install_packages.py +++ b/packages/http-client-python/tests/install_packages.py @@ -13,11 +13,6 @@ import os import subprocess import sys -from pathlib import Path -from types import SimpleNamespace - -sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "eng" / "scripts" / "setup")) -from package_manager import detect_package_manager, upgrade_tooling def _find_packages(generated_dir): @@ -74,9 +69,6 @@ def install_packages(flavor, tests_dir): print(f"Installing {len(packages)} packages from {generated_dir}") use_wheels = os.path.isdir(wheel_dir) and bool(glob.glob(os.path.join(wheel_dir, "*.whl"))) - if sys.prefix != sys.base_prefix: - upgrade_tooling(SimpleNamespace(env_exe=sys.executable), detect_package_manager()) - uv_command = [sys.executable, "-m", "uv"] if sys.prefix != sys.base_prefix else ["uv"] use_uv = True if use_wheels: @@ -86,7 +78,7 @@ def install_packages(flavor, tests_dir): wheel_files = glob.glob(os.path.join(wheel_dir, "*.whl")) print(f" Using {len(wheel_files)} pre-built wheels from .wheels/{flavor}/") try: - cmd = uv_command + ["pip", "install", "--no-deps", "--no-index", "--find-links", wheel_dir] + wheel_files + cmd = ["uv", "pip", "install", "--no-deps", "--no-index", "--find-links", wheel_dir] + wheel_files subprocess.run(cmd, check=True) print(f"Successfully installed {len(wheel_files)} packages") return @@ -108,7 +100,7 @@ def install_packages(flavor, tests_dir): for i in range(0, len(packages), batch_size): batch = packages[i:i + batch_size] if use_uv: - cmd = uv_command + ["pip", "install", "--no-deps", "--cache-dir", cache_dir] + batch + cmd = ["uv", "pip", "install", "--no-deps", "--cache-dir", cache_dir] + batch else: cmd = [sys.executable, "-m", "pip", "install", "--no-deps"] + batch try: From c1a230e960f0099a2c0c01b69ac5af6fc5f9f095 Mon Sep 17 00:00:00 2001 From: l0lawrence Date: Thu, 22 Oct 2026 14:30:00 -0700 Subject: [PATCH 5/6] revert(http-client-python): restore original wheel build command Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../http-client-python/tests/install_packages.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/http-client-python/tests/install_packages.py b/packages/http-client-python/tests/install_packages.py index 2dfabb5542d..ae7862e33fd 100644 --- a/packages/http-client-python/tests/install_packages.py +++ b/packages/http-client-python/tests/install_packages.py @@ -43,10 +43,16 @@ def build_wheels(flavor, tests_dir): batch_size = 50 for i in range(0, len(packages), batch_size): batch = packages[i:i + batch_size] - subprocess.run( - [sys.executable, "-m", "pip", "wheel", "--no-deps", "--wheel-dir", wheel_dir] + batch, - check=True, - ) + try: + subprocess.run( + ["uv", "pip", "wheel", "--no-deps", "--wheel-dir", wheel_dir] + batch, + check=True, + ) + except FileNotFoundError: + subprocess.run( + [sys.executable, "-m", "pip", "wheel", "--no-deps", "--wheel-dir", wheel_dir] + batch, + check=True, + ) wheel_count = len(glob.glob(os.path.join(wheel_dir, "*.whl"))) print(f"Built {wheel_count} wheels for {flavor}") From a4e7775109bf90d93884ff753bb521eac2906aed Mon Sep 17 00:00:00 2001 From: l0lawrence Date: Thu, 22 Oct 2026 14:44:39 -0700 Subject: [PATCH 6/6] fix(http-client-python): use pip for wheel builds Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../http-client-python/tests/install_packages.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/http-client-python/tests/install_packages.py b/packages/http-client-python/tests/install_packages.py index ae7862e33fd..2dfabb5542d 100644 --- a/packages/http-client-python/tests/install_packages.py +++ b/packages/http-client-python/tests/install_packages.py @@ -43,16 +43,10 @@ def build_wheels(flavor, tests_dir): batch_size = 50 for i in range(0, len(packages), batch_size): batch = packages[i:i + batch_size] - try: - subprocess.run( - ["uv", "pip", "wheel", "--no-deps", "--wheel-dir", wheel_dir] + batch, - check=True, - ) - except FileNotFoundError: - subprocess.run( - [sys.executable, "-m", "pip", "wheel", "--no-deps", "--wheel-dir", wheel_dir] + batch, - check=True, - ) + subprocess.run( + [sys.executable, "-m", "pip", "wheel", "--no-deps", "--wheel-dir", wheel_dir] + batch, + check=True, + ) wheel_count = len(glob.glob(os.path.join(wheel_dir, "*.whl"))) print(f"Built {wheel_count} wheels for {flavor}")