From 1cc7a4cfe26f599dcd893048395f201651ea0a05 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Fri, 3 Oct 2025 14:52:56 -0700 Subject: [PATCH 01/17] base --- eng/tools/azure-sdk-tools/azpysdk/devtest.py | 47 ++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 eng/tools/azure-sdk-tools/azpysdk/devtest.py diff --git a/eng/tools/azure-sdk-tools/azpysdk/devtest.py b/eng/tools/azure-sdk-tools/azpysdk/devtest.py new file mode 100644 index 000000000000..f1ddbc839f78 --- /dev/null +++ b/eng/tools/azure-sdk-tools/azpysdk/devtest.py @@ -0,0 +1,47 @@ +import argparse +import os +import sys + +from typing import Optional, List +import subprocess +from subprocess import CalledProcessError, check_call + +from .Check import Check +from ci_tools.functions import install_into_venv, get_pip_command +from ci_tools.scenario.generation import create_package_and_install +from ci_tools.variables import discover_repo_root, in_ci, set_envvar_defaults +from ci_tools.environment_exclusions import is_check_enabled +from ci_tools.logging import logger, run_logged + +class devtest(Check): + def __init__(self) -> None: + super().__init__() + + def register( + self, subparsers: "argparse._SubParsersAction", parent_parsers: Optional[List[argparse.ArgumentParser]] = None + ) -> None: + """Register the devtest check. The devtest check installs devtest and runs devtest against the target package.""" + parents = parent_parsers or [] + p = subparsers.add_parser("devtest", parents=parents, help="Run the devtest check to test a package against dependencies installed from a dev index") + p.set_defaults(func=self.run) + + def run(self, args: argparse.Namespace) -> int: + """Run the devtest check command.""" + logger.info("Running devtest check...") + + set_envvar_defaults() + targeted = self.get_targeted_directories(args) + + results: List[int] = [] + + for parsed in targeted: + package_dir = parsed.folder + package_name = parsed.name + executable, staging_directory = self.get_executable(args.isolate, args.command, sys.executable, package_dir) + logger.info(f"Processing {package_name} for devtest check") + + # install dependencies + self.install_dev_reqs(executable, args, package_dir) + + + return max(results) if results else 0 From 0386b721f1ee1c2dcadb95b44919d502c24edf0d Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:18:40 -0800 Subject: [PATCH 02/17] minor --- eng/tools/azure-sdk-tools/azpysdk/devtest.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/eng/tools/azure-sdk-tools/azpysdk/devtest.py b/eng/tools/azure-sdk-tools/azpysdk/devtest.py index f1ddbc839f78..a9c7f94ed8e7 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/devtest.py +++ b/eng/tools/azure-sdk-tools/azpysdk/devtest.py @@ -13,6 +13,8 @@ from ci_tools.environment_exclusions import is_check_enabled from ci_tools.logging import logger, run_logged +REPO_ROOT = discover_repo_root() + class devtest(Check): def __init__(self) -> None: super().__init__() @@ -20,7 +22,7 @@ def __init__(self) -> None: def register( self, subparsers: "argparse._SubParsersAction", parent_parsers: Optional[List[argparse.ArgumentParser]] = None ) -> None: - """Register the devtest check. The devtest check installs devtest and runs devtest against the target package.""" + """Register the devtest check. The devtest check tests a package against dependencies installed from a dev index.""" parents = parent_parsers or [] p = subparsers.add_parser("devtest", parents=parents, help="Run the devtest check to test a package against dependencies installed from a dev index") p.set_defaults(func=self.run) @@ -43,5 +45,19 @@ def run(self, args: argparse.Namespace) -> int: # install dependencies self.install_dev_reqs(executable, args, package_dir) + create_package_and_install( + distribution_directory=staging_directory, + target_setup=package_dir, + skip_install=False, + cache_dir=None, + work_dir=staging_directory, + force_create=False, + package_type="sdist", + pre_download_disabled=False, + python_executable=executable, + ) + + # install dev build dependency + return max(results) if results else 0 From d3e1db3c6d83ee16ecae3a3f2a03ac203afdbc38 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Thu, 4 Dec 2025 15:57:57 -0800 Subject: [PATCH 03/17] move dependency installs --- doc/tool_usage_guide.md | 1 + eng/tools/azure-sdk-tools/azpysdk/devtest.py | 78 +++++++++++++++++++- eng/tools/azure-sdk-tools/azpysdk/main.py | 2 + 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/doc/tool_usage_guide.md b/doc/tool_usage_guide.md index 084ad53493e3..d5c5db50d3b2 100644 --- a/doc/tool_usage_guide.md +++ b/doc/tool_usage_guide.md @@ -30,6 +30,7 @@ This repo is currently migrating all checks from a slower `tox`-based framework, |`import_all`| Installs the package w/ default dependencies, then attempts to `import *` from the base namespace. Ensures that all imports will resolve after a base install and import. | `azpysdk import_all .` | |`generate`| Regenerates the code. | `azpysdk generate .` | |`breaking`| Checks for breaking changes. | `azpysdk breaking .` | +|`devtest`| Tests a package against dependencies installed from a dev index. | `azpysdk devtest .` | ## Common arguments diff --git a/eng/tools/azure-sdk-tools/azpysdk/devtest.py b/eng/tools/azure-sdk-tools/azpysdk/devtest.py index a9c7f94ed8e7..66375cd33793 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/devtest.py +++ b/eng/tools/azure-sdk-tools/azpysdk/devtest.py @@ -1,19 +1,89 @@ import argparse -import os import sys +import os from typing import Optional, List import subprocess from subprocess import CalledProcessError, check_call from .Check import Check -from ci_tools.functions import install_into_venv, get_pip_command +from ci_tools.functions import install_into_venv, get_pip_command, discover_targeted_packages from ci_tools.scenario.generation import create_package_and_install from ci_tools.variables import discover_repo_root, in_ci, set_envvar_defaults from ci_tools.environment_exclusions import is_check_enabled from ci_tools.logging import logger, run_logged REPO_ROOT = discover_repo_root() +common_task_path = os.path.abspath(os.path.join(REPO_ROOT, "scripts", "devops_tasks")) +sys.path.append(common_task_path) + +from common_tasks import get_installed_packages + +EXCLUDED_PKGS = [ + "azure-common", +] + +# index URL to devops feed +DEV_INDEX_URL = "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-python/pypi/simple" + +def get_installed_azure_packages(pkg_name_to_exclude): + # This method returns a list of installed azure sdk packages + installed_pkgs = [ + p.split("==")[0] for p in get_installed_packages() if p.startswith("azure-") + ] + + # Get valid list of Azure SDK packages in repo + pkgs = discover_targeted_packages("", REPO_ROOT) + valid_azure_packages = [ + os.path.basename(p) for p in pkgs if "mgmt" not in p and "-nspkg" not in p + ] + + # Filter current package and any exlcuded package + pkg_names = [ + p + for p in installed_pkgs + if p in valid_azure_packages + and p != pkg_name_to_exclude + and p not in EXCLUDED_PKGS + ] + + logger.info("Installed azure sdk packages: %s", pkg_names) + return pkg_names + +def uninstall_packages(packages): + # This method uninstall list of given packages so dev build version can be reinstalled + commands = get_pip_command() + commands.append("uninstall") + + logger.info("Uninstalling packages: %s", packages) + commands.extend(packages) + # Pass Uninstall confirmation + commands.append("--yes") + check_call(commands) + logger.info("Uninstalled packages") + +def install_packages(packages): + # install list of given packages from devops feed + + commands = get_pip_command() + commands.append("install") + + logger.info("Installing dev build version for packages: %s", packages) + commands.extend(packages) + commands.extend( + [ + "--index-url", + DEV_INDEX_URL, + ] + ) + # install dev build of azure packages + check_call(commands) + +def install_dev_build_packages(pkg_name_to_exclude): + # Uninstall GA version and reinstall dev build version of dependent packages + azure_pkgs = get_installed_azure_packages(pkg_name_to_exclude) + uninstall_packages(azure_pkgs) + install_packages(azure_pkgs) class devtest(Check): def __init__(self) -> None: @@ -57,7 +127,9 @@ def run(self, args: argparse.Namespace) -> int: python_executable=executable, ) - # install dev build dependency + install_dev_build_packages(package_name) + + # invoke pytest return max(results) if results else 0 diff --git a/eng/tools/azure-sdk-tools/azpysdk/main.py b/eng/tools/azure-sdk-tools/azpysdk/main.py index 0b4c9753b4b2..719ca5429652 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/main.py +++ b/eng/tools/azure-sdk-tools/azpysdk/main.py @@ -32,6 +32,7 @@ from .verify_keywords import verify_keywords from .generate import generate from .breaking import breaking +from .devtest import devtest from ci_tools.logging import configure_logging, logger @@ -95,6 +96,7 @@ def build_parser() -> argparse.ArgumentParser: verify_keywords().register(subparsers, [common]) generate().register(subparsers, [common]) breaking().register(subparsers, [common]) + devtest().register(subparsers, [common]) return parser From a15c0846c787abff2f3236ea2e6c42a030acfb38 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Fri, 5 Dec 2025 09:08:29 -0800 Subject: [PATCH 04/17] working --- eng/tools/azure-sdk-tools/azpysdk/devtest.py | 58 +++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/eng/tools/azure-sdk-tools/azpysdk/devtest.py b/eng/tools/azure-sdk-tools/azpysdk/devtest.py index 66375cd33793..db2dd4defd1d 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/devtest.py +++ b/eng/tools/azure-sdk-tools/azpysdk/devtest.py @@ -7,7 +7,7 @@ from subprocess import CalledProcessError, check_call from .Check import Check -from ci_tools.functions import install_into_venv, get_pip_command, discover_targeted_packages +from ci_tools.functions import install_into_venv, is_error_code_5_allowed, get_pip_command, discover_targeted_packages from ci_tools.scenario.generation import create_package_and_install from ci_tools.variables import discover_repo_root, in_ci, set_envvar_defaults from ci_tools.environment_exclusions import is_check_enabled @@ -26,6 +26,8 @@ # index URL to devops feed DEV_INDEX_URL = "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-python/pypi/simple" +TEST_TOOLS_REQUIREMENTS = os.path.join(REPO_ROOT, "eng/test_tools.txt") + def get_installed_azure_packages(pkg_name_to_exclude): # This method returns a list of installed azure sdk packages installed_pkgs = [ @@ -96,6 +98,11 @@ def register( parents = parent_parsers or [] p = subparsers.add_parser("devtest", parents=parents, help="Run the devtest check to test a package against dependencies installed from a dev index") p.set_defaults(func=self.run) + p.add_argument( + "--pytest-args", + nargs=argparse.REMAINDER, + help="Additional arguments forwarded to pytest.", + ) def run(self, args: argparse.Namespace) -> int: """Run the devtest check command.""" @@ -127,9 +134,56 @@ def run(self, args: argparse.Namespace) -> int: python_executable=executable, ) + if os.path.exists(TEST_TOOLS_REQUIREMENTS): + install_into_venv(executable, ["-r", TEST_TOOLS_REQUIREMENTS], package_dir) + else: + logger.warning(f"Test tools requirements file not found at {TEST_TOOLS_REQUIREMENTS}.") + install_dev_build_packages(package_name) - # invoke pytest + pytest_args = self._build_pytest_args(package_dir, args) + + pytest_result = self.run_venv_command( + executable, + ["-m", "pytest", *pytest_args], + cwd=package_dir, + immediately_dump=True + ) + + if pytest_result.returncode != 0: + if pytest_result.returncode == 5 and is_error_code_5_allowed(package_dir, package_name): + logger.info( + "pytest exited with code 5 for %s, which is allowed for management or opt-out packages.", + package_name, + ) + # Align with tox: skip coverage when tests are skipped entirely + continue + logger.error(f"pytest failed for {package_name} with exit code {pytest_result.returncode}.") + results.append(pytest_result.returncode) return max(results) if results else 0 + + def _build_pytest_args(self, package_dir: str, args: argparse.Namespace) -> List[str]: + log_level = os.getenv("PYTEST_LOG_LEVEL", "51") + junit_path = os.path.join(package_dir, f"test-junit-{args.command}.xml") + + default_args = [ + "-rsfE", + f"--junitxml={junit_path}", + "--verbose", + "--cov-branch", + "--durations=10", + "--ignore=azure", + "--ignore=.tox", + "--ignore-glob=.venv*", + "--ignore=build", + "--ignore=.eggs", + "--ignore=samples", + f"--log-cli-level={log_level}", + ] + + additional = args.pytest_args if args.pytest_args else [] + + return [*default_args, *additional, package_dir] + \ No newline at end of file From c0db82531d4e8d26cd75a598137db0d070714591 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Fri, 5 Dec 2025 09:10:58 -0800 Subject: [PATCH 05/17] proxy url --- eng/tools/azure-sdk-tools/azpysdk/devtest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/tools/azure-sdk-tools/azpysdk/devtest.py b/eng/tools/azure-sdk-tools/azpysdk/devtest.py index db2dd4defd1d..fad7e91001ea 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/devtest.py +++ b/eng/tools/azure-sdk-tools/azpysdk/devtest.py @@ -108,7 +108,7 @@ def run(self, args: argparse.Namespace) -> int: """Run the devtest check command.""" logger.info("Running devtest check...") - set_envvar_defaults() + set_envvar_defaults({"PROXY_URL": "http://localhost:5002"}) targeted = self.get_targeted_directories(args) results: List[int] = [] From 3fac8e96d8c6c1a5cce92d418cc2359d4f07f3c8 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Fri, 5 Dec 2025 13:00:25 -0800 Subject: [PATCH 06/17] refactor pytest args --- eng/tools/azure-sdk-tools/azpysdk/Check.py | 30 +++++++++++ eng/tools/azure-sdk-tools/azpysdk/devtest.py | 54 +++++--------------- eng/tools/azure-sdk-tools/azpysdk/whl.py | 23 --------- 3 files changed, 44 insertions(+), 63 deletions(-) diff --git a/eng/tools/azure-sdk-tools/azpysdk/Check.py b/eng/tools/azure-sdk-tools/azpysdk/Check.py index 212e089b82d0..2bddca212d1a 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/Check.py +++ b/eng/tools/azure-sdk-tools/azpysdk/Check.py @@ -248,3 +248,33 @@ def pip_freeze(self, executable: str) -> None: logger.error(f"Failed to run pip freeze: {e}") logger.error(e.stdout) logger.error(e.stderr) + + def _build_pytest_args(self, package_dir: str, args: argparse.Namespace) -> List[str]: + """ + Builds the pytest arguments used for the given package directory. + + :param package_dir: The package directory to build pytest args for. + :param args: The argparse.Namespace object containing command-line arguments. + :return: A list of pytest arguments. + """ + log_level = os.getenv("PYTEST_LOG_LEVEL", "51") + junit_path = os.path.join(package_dir, f"test-junit-{args.command}.xml") + + default_args = [ + "-rsfE", + f"--junitxml={junit_path}", + "--verbose", + "--cov-branch", + "--durations=10", + "--ignore=azure", + "--ignore=.tox", + "--ignore-glob=.venv*", + "--ignore=build", + "--ignore=.eggs", + "--ignore=samples", + f"--log-cli-level={log_level}", + ] + + additional = args.pytest_args if args.pytest_args else [] + + return [*default_args, *additional, package_dir] diff --git a/eng/tools/azure-sdk-tools/azpysdk/devtest.py b/eng/tools/azure-sdk-tools/azpysdk/devtest.py index fad7e91001ea..5866f6291c7f 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/devtest.py +++ b/eng/tools/azure-sdk-tools/azpysdk/devtest.py @@ -28,30 +28,24 @@ TEST_TOOLS_REQUIREMENTS = os.path.join(REPO_ROOT, "eng/test_tools.txt") + def get_installed_azure_packages(pkg_name_to_exclude): # This method returns a list of installed azure sdk packages - installed_pkgs = [ - p.split("==")[0] for p in get_installed_packages() if p.startswith("azure-") - ] + installed_pkgs = [p.split("==")[0] for p in get_installed_packages() if p.startswith("azure-")] # Get valid list of Azure SDK packages in repo pkgs = discover_targeted_packages("", REPO_ROOT) - valid_azure_packages = [ - os.path.basename(p) for p in pkgs if "mgmt" not in p and "-nspkg" not in p - ] + valid_azure_packages = [os.path.basename(p) for p in pkgs if "mgmt" not in p and "-nspkg" not in p] # Filter current package and any exlcuded package pkg_names = [ - p - for p in installed_pkgs - if p in valid_azure_packages - and p != pkg_name_to_exclude - and p not in EXCLUDED_PKGS + p for p in installed_pkgs if p in valid_azure_packages and p != pkg_name_to_exclude and p not in EXCLUDED_PKGS ] logger.info("Installed azure sdk packages: %s", pkg_names) return pkg_names + def uninstall_packages(packages): # This method uninstall list of given packages so dev build version can be reinstalled commands = get_pip_command() @@ -64,6 +58,7 @@ def uninstall_packages(packages): check_call(commands) logger.info("Uninstalled packages") + def install_packages(packages): # install list of given packages from devops feed @@ -81,12 +76,14 @@ def install_packages(packages): # install dev build of azure packages check_call(commands) + def install_dev_build_packages(pkg_name_to_exclude): # Uninstall GA version and reinstall dev build version of dependent packages azure_pkgs = get_installed_azure_packages(pkg_name_to_exclude) uninstall_packages(azure_pkgs) install_packages(azure_pkgs) + class devtest(Check): def __init__(self) -> None: super().__init__() @@ -96,7 +93,11 @@ def register( ) -> None: """Register the devtest check. The devtest check tests a package against dependencies installed from a dev index.""" parents = parent_parsers or [] - p = subparsers.add_parser("devtest", parents=parents, help="Run the devtest check to test a package against dependencies installed from a dev index") + p = subparsers.add_parser( + "devtest", + parents=parents, + help="Run the devtest check to test a package against dependencies installed from a dev index", + ) p.set_defaults(func=self.run) p.add_argument( "--pytest-args", @@ -144,10 +145,7 @@ def run(self, args: argparse.Namespace) -> int: pytest_args = self._build_pytest_args(package_dir, args) pytest_result = self.run_venv_command( - executable, - ["-m", "pytest", *pytest_args], - cwd=package_dir, - immediately_dump=True + executable, ["-m", "pytest", *pytest_args], cwd=package_dir, immediately_dump=True ) if pytest_result.returncode != 0: @@ -163,27 +161,3 @@ def run(self, args: argparse.Namespace) -> int: results.append(pytest_result.returncode) return max(results) if results else 0 - - def _build_pytest_args(self, package_dir: str, args: argparse.Namespace) -> List[str]: - log_level = os.getenv("PYTEST_LOG_LEVEL", "51") - junit_path = os.path.join(package_dir, f"test-junit-{args.command}.xml") - - default_args = [ - "-rsfE", - f"--junitxml={junit_path}", - "--verbose", - "--cov-branch", - "--durations=10", - "--ignore=azure", - "--ignore=.tox", - "--ignore-glob=.venv*", - "--ignore=build", - "--ignore=.eggs", - "--ignore=samples", - f"--log-cli-level={log_level}", - ] - - additional = args.pytest_args if args.pytest_args else [] - - return [*default_args, *additional, package_dir] - \ No newline at end of file diff --git a/eng/tools/azure-sdk-tools/azpysdk/whl.py b/eng/tools/azure-sdk-tools/azpysdk/whl.py index fbd7850f8e5c..515ea0cdf645 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/whl.py +++ b/eng/tools/azure-sdk-tools/azpysdk/whl.py @@ -132,26 +132,3 @@ def _install_common_requirements(self, executable: str, package_dir: str) -> Non install_into_venv(executable, ["-r", TEST_TOOLS_REQUIREMENTS], package_dir) else: logger.warning(f"Test tools requirements file not found at {TEST_TOOLS_REQUIREMENTS}.") - - def _build_pytest_args(self, package_dir: str, args: argparse.Namespace) -> List[str]: - log_level = os.getenv("PYTEST_LOG_LEVEL", "51") - junit_path = os.path.join(package_dir, f"test-junit-{args.command}.xml") - - default_args = [ - "-rsfE", - f"--junitxml={junit_path}", - "--verbose", - "--cov-branch", - "--durations=10", - "--ignore=azure", - "--ignore=.tox", - "--ignore-glob=.venv*", - "--ignore=build", - "--ignore=.eggs", - "--ignore=samples", - f"--log-cli-level={log_level}", - ] - - additional = args.pytest_args if args.pytest_args else [] - - return [*default_args, *additional, package_dir] From e1c2056bf9483f562a2b18f61842c9ce14cb02c6 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Fri, 5 Dec 2025 13:24:47 -0800 Subject: [PATCH 07/17] uv fix --- eng/tools/azure-sdk-tools/azpysdk/devtest.py | 29 +++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/eng/tools/azure-sdk-tools/azpysdk/devtest.py b/eng/tools/azure-sdk-tools/azpysdk/devtest.py index 5866f6291c7f..641be0ccaa6f 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/devtest.py +++ b/eng/tools/azure-sdk-tools/azpysdk/devtest.py @@ -3,15 +3,13 @@ import os from typing import Optional, List -import subprocess -from subprocess import CalledProcessError, check_call +from subprocess import check_call from .Check import Check from ci_tools.functions import install_into_venv, is_error_code_5_allowed, get_pip_command, discover_targeted_packages from ci_tools.scenario.generation import create_package_and_install -from ci_tools.variables import discover_repo_root, in_ci, set_envvar_defaults -from ci_tools.environment_exclusions import is_check_enabled -from ci_tools.logging import logger, run_logged +from ci_tools.variables import discover_repo_root, set_envvar_defaults +from ci_tools.logging import logger REPO_ROOT = discover_repo_root() common_task_path = os.path.abspath(os.path.join(REPO_ROOT, "scripts", "devops_tasks")) @@ -29,7 +27,7 @@ TEST_TOOLS_REQUIREMENTS = os.path.join(REPO_ROOT, "eng/test_tools.txt") -def get_installed_azure_packages(pkg_name_to_exclude): +def get_installed_azure_packages(pkg_name_to_exclude: str) -> List[str]: # This method returns a list of installed azure sdk packages installed_pkgs = [p.split("==")[0] for p in get_installed_packages() if p.startswith("azure-")] @@ -46,26 +44,37 @@ def get_installed_azure_packages(pkg_name_to_exclude): return pkg_names -def uninstall_packages(packages): +def uninstall_packages(packages: List[str]): # This method uninstall list of given packages so dev build version can be reinstalled + if len(packages) == 0: + logger.warning("No packages to uninstall.") + return + commands = get_pip_command() commands.append("uninstall") logger.info("Uninstalling packages: %s", packages) + commands.extend(packages) # Pass Uninstall confirmation - commands.append("--yes") + if commands[0] != "uv": + commands.append("--yes") + check_call(commands) logger.info("Uninstalled packages") -def install_packages(packages): +def install_packages(packages: List[str]): # install list of given packages from devops feed + if len(packages) == 0: + logger.warning("No packages to install.") + return commands = get_pip_command() commands.append("install") logger.info("Installing dev build version for packages: %s", packages) + commands.extend(packages) commands.extend( [ @@ -77,7 +86,7 @@ def install_packages(packages): check_call(commands) -def install_dev_build_packages(pkg_name_to_exclude): +def install_dev_build_packages(pkg_name_to_exclude: str): # Uninstall GA version and reinstall dev build version of dependent packages azure_pkgs = get_installed_azure_packages(pkg_name_to_exclude) uninstall_packages(azure_pkgs) From 9b8350459a5b00cbae5469e06af777167e8cc270 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Fri, 5 Dec 2025 13:45:59 -0800 Subject: [PATCH 08/17] more uv fix! --- eng/tools/azure-sdk-tools/azpysdk/devtest.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/eng/tools/azure-sdk-tools/azpysdk/devtest.py b/eng/tools/azure-sdk-tools/azpysdk/devtest.py index 641be0ccaa6f..65eb9a33843c 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/devtest.py +++ b/eng/tools/azure-sdk-tools/azpysdk/devtest.py @@ -64,33 +64,31 @@ def uninstall_packages(packages: List[str]): logger.info("Uninstalled packages") -def install_packages(packages: List[str]): +def install_packages(executable: str, packages: List[str], working_directory: str): # install list of given packages from devops feed if len(packages) == 0: logger.warning("No packages to install.") return - commands = get_pip_command() - commands.append("install") - logger.info("Installing dev build version for packages: %s", packages) - commands.extend(packages) + commands = packages commands.extend( [ "--index-url", DEV_INDEX_URL, ] ) + # install dev build of azure packages - check_call(commands) + install_into_venv(executable, commands, working_directory) -def install_dev_build_packages(pkg_name_to_exclude: str): +def install_dev_build_packages(executable: str, pkg_name_to_exclude: str, working_directory: str): # Uninstall GA version and reinstall dev build version of dependent packages azure_pkgs = get_installed_azure_packages(pkg_name_to_exclude) uninstall_packages(azure_pkgs) - install_packages(azure_pkgs) + install_packages(executable, azure_pkgs, working_directory) class devtest(Check): @@ -149,7 +147,7 @@ def run(self, args: argparse.Namespace) -> int: else: logger.warning(f"Test tools requirements file not found at {TEST_TOOLS_REQUIREMENTS}.") - install_dev_build_packages(package_name) + install_dev_build_packages(executable, package_name, package_dir) pytest_args = self._build_pytest_args(package_dir, args) From 5d5570934237cfb77ef2bcae6a55425938594e7b Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Fri, 5 Dec 2025 14:03:21 -0800 Subject: [PATCH 09/17] more uv fix! except...finding pkg names must not be using the right venv... --- eng/tools/azure-sdk-tools/azpysdk/devtest.py | 22 +++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/eng/tools/azure-sdk-tools/azpysdk/devtest.py b/eng/tools/azure-sdk-tools/azpysdk/devtest.py index 65eb9a33843c..48948133846d 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/devtest.py +++ b/eng/tools/azure-sdk-tools/azpysdk/devtest.py @@ -6,7 +6,13 @@ from subprocess import check_call from .Check import Check -from ci_tools.functions import install_into_venv, is_error_code_5_allowed, get_pip_command, discover_targeted_packages +from ci_tools.functions import ( + install_into_venv, + uninstall_from_venv, + is_error_code_5_allowed, + get_pip_command, + discover_targeted_packages, +) from ci_tools.scenario.generation import create_package_and_install from ci_tools.variables import discover_repo_root, set_envvar_defaults from ci_tools.logging import logger @@ -30,7 +36,6 @@ def get_installed_azure_packages(pkg_name_to_exclude: str) -> List[str]: # This method returns a list of installed azure sdk packages installed_pkgs = [p.split("==")[0] for p in get_installed_packages() if p.startswith("azure-")] - # Get valid list of Azure SDK packages in repo pkgs = discover_targeted_packages("", REPO_ROOT) valid_azure_packages = [os.path.basename(p) for p in pkgs if "mgmt" not in p and "-nspkg" not in p] @@ -39,12 +44,12 @@ def get_installed_azure_packages(pkg_name_to_exclude: str) -> List[str]: pkg_names = [ p for p in installed_pkgs if p in valid_azure_packages and p != pkg_name_to_exclude and p not in EXCLUDED_PKGS ] - + breakpoint() logger.info("Installed azure sdk packages: %s", pkg_names) return pkg_names -def uninstall_packages(packages: List[str]): +def uninstall_packages(executable: str, packages: List[str], working_directory: str): # This method uninstall list of given packages so dev build version can be reinstalled if len(packages) == 0: logger.warning("No packages to uninstall.") @@ -59,8 +64,11 @@ def uninstall_packages(packages: List[str]): # Pass Uninstall confirmation if commands[0] != "uv": commands.append("--yes") - - check_call(commands) + else: + commands.append("-y") + print(commands) + print("bros") + uninstall_from_venv(executable, packages, working_directory) logger.info("Uninstalled packages") @@ -87,7 +95,7 @@ def install_packages(executable: str, packages: List[str], working_directory: st def install_dev_build_packages(executable: str, pkg_name_to_exclude: str, working_directory: str): # Uninstall GA version and reinstall dev build version of dependent packages azure_pkgs = get_installed_azure_packages(pkg_name_to_exclude) - uninstall_packages(azure_pkgs) + uninstall_packages(executable, azure_pkgs, working_directory) install_packages(executable, azure_pkgs, working_directory) From e162c6debe041c21540e744ca2293fb5040e4d02 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Fri, 5 Dec 2025 14:53:19 -0800 Subject: [PATCH 10/17] saved by glob --- eng/tools/azure-sdk-tools/azpysdk/devtest.py | 20 ++++++++++++----- .../azure-sdk-tools/ci_tools/functions.py | 22 ++++++++++++++++++- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/eng/tools/azure-sdk-tools/azpysdk/devtest.py b/eng/tools/azure-sdk-tools/azpysdk/devtest.py index 48948133846d..9486cca46df0 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/devtest.py +++ b/eng/tools/azure-sdk-tools/azpysdk/devtest.py @@ -1,6 +1,7 @@ import argparse import sys import os +import glob from typing import Optional, List from subprocess import check_call @@ -33,9 +34,15 @@ TEST_TOOLS_REQUIREMENTS = os.path.join(REPO_ROOT, "eng/test_tools.txt") -def get_installed_azure_packages(pkg_name_to_exclude: str) -> List[str]: +def get_installed_azure_packages(executable: str, pkg_name_to_exclude: str) -> List[str]: # This method returns a list of installed azure sdk packages - installed_pkgs = [p.split("==")[0] for p in get_installed_packages() if p.startswith("azure-")] + + venv_root = os.path.dirname(os.path.dirname(executable)) + # Find site-packages directory within the venv + site_packages_pattern = os.path.join(venv_root, "lib", "python*", "site-packages") + site_packages_dirs = glob.glob(site_packages_pattern) + installed_pkgs = [p.split("==")[0] for p in get_installed_packages(site_packages_dirs) if p.startswith("azure-")] + # Get valid list of Azure SDK packages in repo pkgs = discover_targeted_packages("", REPO_ROOT) valid_azure_packages = [os.path.basename(p) for p in pkgs if "mgmt" not in p and "-nspkg" not in p] @@ -44,7 +51,7 @@ def get_installed_azure_packages(pkg_name_to_exclude: str) -> List[str]: pkg_names = [ p for p in installed_pkgs if p in valid_azure_packages and p != pkg_name_to_exclude and p not in EXCLUDED_PKGS ] - breakpoint() + logger.info("Installed azure sdk packages: %s", pkg_names) return pkg_names @@ -66,8 +73,7 @@ def uninstall_packages(executable: str, packages: List[str], working_directory: commands.append("--yes") else: commands.append("-y") - print(commands) - print("bros") + uninstall_from_venv(executable, packages, working_directory) logger.info("Uninstalled packages") @@ -94,7 +100,7 @@ def install_packages(executable: str, packages: List[str], working_directory: st def install_dev_build_packages(executable: str, pkg_name_to_exclude: str, working_directory: str): # Uninstall GA version and reinstall dev build version of dependent packages - azure_pkgs = get_installed_azure_packages(pkg_name_to_exclude) + azure_pkgs = get_installed_azure_packages(executable, pkg_name_to_exclude) uninstall_packages(executable, azure_pkgs, working_directory) install_packages(executable, azure_pkgs, working_directory) @@ -157,6 +163,8 @@ def run(self, args: argparse.Namespace) -> int: install_dev_build_packages(executable, package_name, package_dir) + self.pip_freeze(executable) + pytest_args = self._build_pytest_args(package_dir, args) pytest_result = self.run_venv_command( diff --git a/eng/tools/azure-sdk-tools/ci_tools/functions.py b/eng/tools/azure-sdk-tools/ci_tools/functions.py index 59261bc7113c..75a821e24152 100644 --- a/eng/tools/azure-sdk-tools/ci_tools/functions.py +++ b/eng/tools/azure-sdk-tools/ci_tools/functions.py @@ -565,10 +565,30 @@ def install_into_venv(venv_path_or_executable: str, requirements: List[str], wor if pip_cmd[0] == "uv": cmd += ["--python", py] + # todo: clean this up so that we're using run_logged from #42862 subprocess.check_call(cmd, cwd=working_directory) +def uninstall_from_venv(venv_path_or_executable: str, requirements: List[str], working_directory: str) -> None: + """ + Uninstalls the requirements from an existing venv (venv_path) without activating it. + """ + py = get_venv_python(venv_path_or_executable) + pip_cmd = get_pip_command(py) + + install_targets = [r.strip() for r in requirements] + cmd = pip_cmd + ["uninstall"] + if pip_cmd[0] != "uv": + cmd += ["-y"] + cmd.extend(install_targets) + + if pip_cmd[0] == "uv": + cmd += ["--python", py] + + subprocess.check_call(cmd, cwd=working_directory) + + def pip_install_requirements_file(requirements_file: str, python_executable: Optional[str] = None) -> bool: return pip_install(["-r", requirements_file], True, python_executable) @@ -592,7 +612,7 @@ def get_pip_list_output(python_executable: Optional[str] = None): if stdout and (stderr is None): # this should be compatible with py27 https://docs.python.org/2.7/library/stdtypes.html#str.decode for line in stdout.decode("utf-8").split(os.linesep)[2:]: - if line: + if line and "==" in line: package, version = re.split("==", line) collected_output[package] = version else: From fd34c6e345444136d3054c4f428a62a10be1d8a6 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Fri, 5 Dec 2025 15:03:01 -0800 Subject: [PATCH 11/17] try catch --- eng/tools/azure-sdk-tools/azpysdk/devtest.py | 27 +++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/eng/tools/azure-sdk-tools/azpysdk/devtest.py b/eng/tools/azure-sdk-tools/azpysdk/devtest.py index 9486cca46df0..db344b98cec8 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/devtest.py +++ b/eng/tools/azure-sdk-tools/azpysdk/devtest.py @@ -74,7 +74,11 @@ def uninstall_packages(executable: str, packages: List[str], working_directory: else: commands.append("-y") - uninstall_from_venv(executable, packages, working_directory) + try: + uninstall_from_venv(executable, packages, working_directory) + except Exception as e: + logger.error(f"Failed to uninstall packages: {e}") + raise e logger.info("Uninstalled packages") @@ -95,7 +99,12 @@ def install_packages(executable: str, packages: List[str], working_directory: st ) # install dev build of azure packages - install_into_venv(executable, commands, working_directory) + try: + install_into_venv(executable, commands, working_directory) + except Exception as e: + logger.error(f"Failed to install packages: {e}") + raise e + logger.info("Installed dev build version for packages") def install_dev_build_packages(executable: str, pkg_name_to_exclude: str, working_directory: str): @@ -157,11 +166,21 @@ def run(self, args: argparse.Namespace) -> int: ) if os.path.exists(TEST_TOOLS_REQUIREMENTS): - install_into_venv(executable, ["-r", TEST_TOOLS_REQUIREMENTS], package_dir) + try: + install_into_venv(executable, ["-r", TEST_TOOLS_REQUIREMENTS], package_dir) + except Exception as e: + logger.error(f"Failed to install test tools requirements: {e}") + results.append(1) + continue else: logger.warning(f"Test tools requirements file not found at {TEST_TOOLS_REQUIREMENTS}.") - install_dev_build_packages(executable, package_name, package_dir) + try: + install_dev_build_packages(executable, package_name, package_dir) + except Exception as e: + logger.error(f"Failed to install dev build packages: {e}") + results.append(1) + continue self.pip_freeze(executable) From 9e1e8e13783ee62eaa332c96385437c2fa77b38d Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Fri, 5 Dec 2025 15:04:22 -0800 Subject: [PATCH 12/17] clean --- eng/tools/azure-sdk-tools/azpysdk/devtest.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/eng/tools/azure-sdk-tools/azpysdk/devtest.py b/eng/tools/azure-sdk-tools/azpysdk/devtest.py index db344b98cec8..0b4d0f2dcca7 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/devtest.py +++ b/eng/tools/azure-sdk-tools/azpysdk/devtest.py @@ -182,8 +182,6 @@ def run(self, args: argparse.Namespace) -> int: results.append(1) continue - self.pip_freeze(executable) - pytest_args = self._build_pytest_args(package_dir, args) pytest_result = self.run_venv_command( From 737e7e65ecb891d385b6441d98c055500832da99 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Fri, 5 Dec 2025 15:14:10 -0800 Subject: [PATCH 13/17] oops clean uninstall --- eng/tools/azure-sdk-tools/azpysdk/devtest.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/eng/tools/azure-sdk-tools/azpysdk/devtest.py b/eng/tools/azure-sdk-tools/azpysdk/devtest.py index 0b4d0f2dcca7..0c5cfe451693 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/devtest.py +++ b/eng/tools/azure-sdk-tools/azpysdk/devtest.py @@ -62,18 +62,8 @@ def uninstall_packages(executable: str, packages: List[str], working_directory: logger.warning("No packages to uninstall.") return - commands = get_pip_command() - commands.append("uninstall") - logger.info("Uninstalling packages: %s", packages) - commands.extend(packages) - # Pass Uninstall confirmation - if commands[0] != "uv": - commands.append("--yes") - else: - commands.append("-y") - try: uninstall_from_venv(executable, packages, working_directory) except Exception as e: From 937a183007706ffd8c5d6d0916c43925d54bad18 Mon Sep 17 00:00:00 2001 From: jenny <63012604+JennyPng@users.noreply.github.com> Date: Fri, 5 Dec 2025 15:24:28 -0800 Subject: [PATCH 14/17] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- eng/tools/azure-sdk-tools/azpysdk/devtest.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/eng/tools/azure-sdk-tools/azpysdk/devtest.py b/eng/tools/azure-sdk-tools/azpysdk/devtest.py index 0c5cfe451693..ba43a5a40495 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/devtest.py +++ b/eng/tools/azure-sdk-tools/azpysdk/devtest.py @@ -4,7 +4,6 @@ import glob from typing import Optional, List -from subprocess import check_call from .Check import Check from ci_tools.functions import ( @@ -35,8 +34,13 @@ def get_installed_azure_packages(executable: str, pkg_name_to_exclude: str) -> List[str]: - # This method returns a list of installed azure sdk packages + """ + Returns a list of installed Azure SDK packages in the venv, excluding specified packages. + :param executable: Path to the Python executable in the venv. + :param pkg_name_to_exclude: Package name to exclude from the result. + :return: List of installed Azure SDK package names. + """ venv_root = os.path.dirname(os.path.dirname(executable)) # Find site-packages directory within the venv site_packages_pattern = os.path.join(venv_root, "lib", "python*", "site-packages") @@ -47,7 +51,7 @@ def get_installed_azure_packages(executable: str, pkg_name_to_exclude: str) -> L pkgs = discover_targeted_packages("", REPO_ROOT) valid_azure_packages = [os.path.basename(p) for p in pkgs if "mgmt" not in p and "-nspkg" not in p] - # Filter current package and any exlcuded package + # Filter current package and any excluded package pkg_names = [ p for p in installed_pkgs if p in valid_azure_packages and p != pkg_name_to_exclude and p not in EXCLUDED_PKGS ] @@ -57,7 +61,15 @@ def get_installed_azure_packages(executable: str, pkg_name_to_exclude: str) -> L def uninstall_packages(executable: str, packages: List[str], working_directory: str): - # This method uninstall list of given packages so dev build version can be reinstalled + """ + Uninstalls a list of packages from the virtual environment so dev build versions can be reinstalled. + + :param executable: Path to the Python executable in the virtual environment. + :param packages: List of package names to uninstall. + :param working_directory: Directory from which to run the uninstall command. + :raises Exception: If uninstallation fails. + :return: None + """ if len(packages) == 0: logger.warning("No packages to uninstall.") return From fca56ee5a58f2cadb65f05be2cefe27a19ae4602 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Fri, 5 Dec 2025 15:33:59 -0800 Subject: [PATCH 15/17] minor code review fixes --- eng/tools/azure-sdk-tools/azpysdk/devtest.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/eng/tools/azure-sdk-tools/azpysdk/devtest.py b/eng/tools/azure-sdk-tools/azpysdk/devtest.py index ba43a5a40495..7b29a0b7f6c0 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/devtest.py +++ b/eng/tools/azure-sdk-tools/azpysdk/devtest.py @@ -10,7 +10,6 @@ install_into_venv, uninstall_from_venv, is_error_code_5_allowed, - get_pip_command, discover_targeted_packages, ) from ci_tools.scenario.generation import create_package_and_install @@ -85,20 +84,23 @@ def uninstall_packages(executable: str, packages: List[str], working_directory: def install_packages(executable: str, packages: List[str], working_directory: str): - # install list of given packages from devops feed + """ + Installs a list of packages from the devops feed into the virtual environment. + + :param executable: Path to the Python executable in the virtual environment. + :param packages: List of package names to install. + :param working_directory: Directory from which to run the install command. + :raises Exception: If installation fails. + :return: None + """ + if len(packages) == 0: logger.warning("No packages to install.") return logger.info("Installing dev build version for packages: %s", packages) - commands = packages - commands.extend( - [ - "--index-url", - DEV_INDEX_URL, - ] - ) + commands = [*packages, "--index-url", DEV_INDEX_URL] # install dev build of azure packages try: From 0273a4330ac757ee592e55af922a5f70e09b9015 Mon Sep 17 00:00:00 2001 From: jenny <63012604+JennyPng@users.noreply.github.com> Date: Fri, 5 Dec 2025 15:53:52 -0800 Subject: [PATCH 16/17] cross-platform o_o --- eng/tools/azure-sdk-tools/azpysdk/devtest.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/eng/tools/azure-sdk-tools/azpysdk/devtest.py b/eng/tools/azure-sdk-tools/azpysdk/devtest.py index 7b29a0b7f6c0..e13f119cde01 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/devtest.py +++ b/eng/tools/azure-sdk-tools/azpysdk/devtest.py @@ -42,7 +42,10 @@ def get_installed_azure_packages(executable: str, pkg_name_to_exclude: str) -> L """ venv_root = os.path.dirname(os.path.dirname(executable)) # Find site-packages directory within the venv - site_packages_pattern = os.path.join(venv_root, "lib", "python*", "site-packages") + if os.name == "nt": + site_packages_pattern = os.path.join(venv_root, "Lib", "site-packages") + else: + site_packages_pattern = os.path.join(venv_root, "lib", "python*", "site-packages") site_packages_dirs = glob.glob(site_packages_pattern) installed_pkgs = [p.split("==")[0] for p in get_installed_packages(site_packages_dirs) if p.startswith("azure-")] From c210cc803df0973f7938e3f8ae5810724cea73c2 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Tue, 16 Dec 2025 13:20:36 -0800 Subject: [PATCH 17/17] try except --- eng/tools/azure-sdk-tools/azpysdk/devtest.py | 37 ++++++++++++------- .../azure-sdk-tools/ci_tools/functions.py | 2 +- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/eng/tools/azure-sdk-tools/azpysdk/devtest.py b/eng/tools/azure-sdk-tools/azpysdk/devtest.py index e13f119cde01..a45614d77a3f 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/devtest.py +++ b/eng/tools/azure-sdk-tools/azpysdk/devtest.py @@ -1,4 +1,5 @@ import argparse +from subprocess import CalledProcessError import sys import os import glob @@ -158,19 +159,29 @@ def run(self, args: argparse.Namespace) -> int: logger.info(f"Processing {package_name} for devtest check") # install dependencies - self.install_dev_reqs(executable, args, package_dir) - - create_package_and_install( - distribution_directory=staging_directory, - target_setup=package_dir, - skip_install=False, - cache_dir=None, - work_dir=staging_directory, - force_create=False, - package_type="sdist", - pre_download_disabled=False, - python_executable=executable, - ) + try: + self.install_dev_reqs(executable, args, package_dir) + except CalledProcessError as e: + logger.error(f"Failed to install dev requirements: {e}") + results.append(1) + continue + + try: + create_package_and_install( + distribution_directory=staging_directory, + target_setup=package_dir, + skip_install=False, + cache_dir=None, + work_dir=staging_directory, + force_create=False, + package_type="sdist", + pre_download_disabled=False, + python_executable=executable, + ) + except CalledProcessError as e: + logger.error(f"Failed to create and install package {package_name}: {e}") + results.append(1) + continue if os.path.exists(TEST_TOOLS_REQUIREMENTS): try: diff --git a/eng/tools/azure-sdk-tools/ci_tools/functions.py b/eng/tools/azure-sdk-tools/ci_tools/functions.py index 75a821e24152..13b43a57fb0e 100644 --- a/eng/tools/azure-sdk-tools/ci_tools/functions.py +++ b/eng/tools/azure-sdk-tools/ci_tools/functions.py @@ -519,7 +519,7 @@ def pip_uninstall(requirements: List[str], python_executable: str) -> bool: """ Attempts to invoke an install operation using the invoking python's pip. Empty requirements are auto-success. """ - # we do not use get_pip_command here because uv pip doesn't have an uninstall command + # use uninstall_from_venv() for uv venvs exe = python_executable or sys.executable command = [exe, "-m", "pip", "uninstall", "-y"]