Skip to content

Commit 7337018

Browse files
authored
[mcp] add more tox validation (#41573)
* thi * tox-uv? * tox-uv option w/ copilot help * this * this * this? * this * lets do this * dotn need * this * merge pip function to one place * comments * formatting * remove * same for next * tox-uv * tox cache * update * this
1 parent fda8ae8 commit 7337018

File tree

10 files changed

+658
-614
lines changed

10 files changed

+658
-614
lines changed

.github/copilot-instructions.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,24 @@ tox -e pyright -c [path to tox.ini] --root .
122122

123123
# Step 3d: Verifytypes
124124
tox -e verifytypes -c [path to tox.ini] --root .
125+
126+
# Step 3e: Sphinx
127+
tox -e sphinx -c [path to tox.ini] --root .
128+
129+
# Step 3f: Mindependency
130+
tox -e mindependency -c [path to tox.ini] --root .
131+
132+
# Step 3g: Bandit
133+
tox -e bandit -c [path to tox.ini] --root .
134+
135+
# Step 3h: Black
136+
tox -e black -c [path to tox.ini] --root .
137+
138+
# Step 3i: Samples
139+
tox -e samples -c [path to tox.ini] --root .
140+
141+
# Step 3j: Breaking
142+
tox -e breaking -c [path to tox.ini] --root .
125143
```
126144

127145
**REQUIREMENTS:**

eng/tools/mcp/azure-sdk-python-mcp/main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
from github import Github
1212
from mcp.server.fastmcp import FastMCP
1313

14+
# Set default pip implementation for tox to use uv
15+
os.environ.setdefault('TOX_PIP_IMPL', 'uv pip')
16+
1417
# Create FastMCP instance
1518
mcp = FastMCP("azure-sdk-python-mcp")
1619

@@ -24,6 +27,7 @@
2427
logger.info(f"Running with Python executable: {sys.executable}")
2528
logger.info(f"Virtual environment path: {os.environ.get('VIRTUAL_ENV', 'Not running in a virtual environment')}")
2629
logger.info(f"Working directory: {os.getcwd()}")
30+
logger.info(f"TOX_PIP_IMPL set to: {os.environ.get('TOX_PIP_IMPL', 'not set')}")
2731

2832
def get_latest_commit(tspurl: str) -> str:
2933
"""Get the latest commit hash for a given TypeSpec config URL.

eng/tools/mcp/azure-sdk-python-mcp/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ dependencies = [
1313
"pylint>=3.3.7",
1414
"tox>=4.26.0",
1515
"httpx>=0.24.0",
16+
"tox-uv>=1.26.0",
1617
]
1718

1819
[project.scripts]

eng/tools/mcp/azure-sdk-python-mcp/uv.lock

Lines changed: 565 additions & 560 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

eng/tox/install_depend_packages.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from packaging.version import Version
2020

2121
from ci_tools.parsing import ParsedSetup, parse_require
22-
from ci_tools.functions import compare_python_version, handle_incompatible_minimum_dev_reqs
22+
from ci_tools.functions import compare_python_version, handle_incompatible_minimum_dev_reqs, get_pip_command
2323

2424
from typing import List
2525

@@ -366,12 +366,8 @@ def filter_dev_requirements(
366366

367367
def install_packages(packages, req_file):
368368
# install list of given packages from PyPI
369-
commands = [
370-
sys.executable,
371-
"-m",
372-
"pip",
373-
"install",
374-
]
369+
commands = get_pip_command()
370+
commands.append("install")
375371

376372
if packages:
377373
commands.extend(packages)

eng/tox/install_dev_build_dependency.py

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
sys.path.append(common_task_path)
2020

2121
from common_tasks import get_installed_packages
22-
from ci_tools.functions import discover_targeted_packages
22+
from ci_tools.functions import discover_targeted_packages, get_pip_command
2323
from ci_tools.parsing import ParsedSetup
2424

2525
EXCLUDED_PKGS = [
@@ -61,12 +61,8 @@ def get_installed_azure_packages(pkg_name_to_exclude):
6161

6262
def uninstall_packages(packages):
6363
# This method uninstall list of given packages so dev build version can be reinstalled
64-
commands = [
65-
sys.executable,
66-
"-m",
67-
"pip",
68-
"uninstall",
69-
]
64+
commands = get_pip_command()
65+
commands.append("uninstall")
7066

7167
logging.info("Uninstalling packages: %s", packages)
7268
commands.extend(packages)
@@ -79,12 +75,8 @@ def uninstall_packages(packages):
7975
def install_packages(packages):
8076
# install list of given packages from devops feed
8177

82-
commands = [
83-
sys.executable,
84-
"-m",
85-
"pip",
86-
"install",
87-
]
78+
commands = get_pip_command()
79+
commands.append("install")
8880

8981
logging.info("Installing dev build version for packages: %s", packages)
9082
commands.extend(packages)

eng/tox/run_verifytypes.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@
2222
from ci_tools.parsing import ParsedSetup
2323
from ci_tools.environment_exclusions import is_check_enabled, is_typing_ignored
2424
from ci_tools.variables import in_ci
25+
from ci_tools.functions import get_pip_command
2526

2627
logging.getLogger().setLevel(logging.INFO)
2728
root_dir = os.path.abspath(os.path.join(os.path.abspath(__file__), "..", "..", ".."))
2829

29-
3030
def install_from_main(setup_path: str) -> None:
3131
path = pathlib.Path(setup_path)
3232
subdirectory = path.relative_to(root_dir)
@@ -51,10 +51,7 @@ def install_from_main(setup_path: str) -> None:
5151

5252
os.chdir(subdirectory)
5353

54-
command = [
55-
sys.executable,
56-
"-m",
57-
"pip",
54+
command = get_pip_command() + [
5855
"install",
5956
".",
6057
"--force-reinstall"

eng/tox/tox.ini

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@
55

66
# In all cases, whenever we install an azure-* package from a requirement (read: not a specific file), the only source will be from the dev feed.
77
# once we've downloaded these dependencies, only then do we install other packages from pypi.
8+
9+
# INSTALLER SELECTION:
10+
# You can control which pip implementation is used by setting the TOX_PIP_IMPL environment variable:
11+
# - TOX_PIP_IMPL=pip (default) - Uses standard pip for package installation
12+
# - TOX_PIP_IMPL=uv - Uses uv pip for faster package installation
13+
#
14+
# Example usage:
15+
# tox -e pylint # Use standard pip (default)
16+
# TOX_PIP_IMPL=pip tox -e pylint # Use standard pip explicitly
17+
# TOX_PIP_IMPL=uv tox -e pylint # Use uv for faster installation
18+
819
[tox]
920
requires=
1021
# Ensure that we're running a version of tox compatible with this config
@@ -13,6 +24,10 @@ requires=
1324
tox>=4.4.10
1425
# note that this envlist is the default set of environments that will run if a target environment is not selected.
1526
envlist = whl,sdist
27+
# Environment variable controlled installer configuration
28+
# Set TOX_PIP_IMPL=uv pip to use uv pip, or TOX_PIP_IMPL=pip (default) to use standard pip
29+
pip_impl = {env:TOX_PIP_IMPL:pip}
30+
pip_command = {[tox]pip_impl}
1631

1732

1833
[tools]
@@ -51,27 +66,28 @@ ignore_args=--ignore=.tox --ignore=build --ignore=.eggs --ignore=samples
5166
default_args = -rsfE --junitxml={tox_root}/test-junit-{envname}.xml --verbose --cov-branch --durations=10 --ignore=azure {[pytest]ignore_args} --log-cli-level={pytest_log_level}
5267

5368
[testenv]
69+
uv_seed = true
5470
parallel_show_output =True
5571
skip_install = true
5672
skipsdist = true
5773
usedevelop = false
5874
passenv = *
5975
download=true
76+
# Allow both pip and uv as external commands since we support both via TOX_PIP_IMPL
77+
allowlist_externals = uv,pip
6078
requires=
6179
{[packaging]pkgs}
6280
setenv =
6381
SPHINX_APIDOC_OPTIONS=members,undoc-members,inherited-members
64-
PIP_EXTRA_INDEX_URL=https://pypi.python.org/simple
6582
PROXY_URL=http://localhost:5000
6683
VIRTUALENV_WHEEL=0.45.1
6784
VIRTUALENV_PIP=24.0
6885
VIRTUALENV_SETUPTOOLS=75.3.2
86+
PIP_EXTRA_INDEX_URL=https://pypi.python.org/simple
6987
deps = {[base]deps}
70-
install_command = python -m pip install {opts} {packages} --cache-dir {tox_root}/../.tox_pip_cache_{envname}
88+
install_command = python -m {[tox]pip_command} install {opts} {packages} --cache-dir {tox_root}/../.tox_pip_cache_{envname}
7189
commands =
72-
python -m pip --version
7390
python {repository_root}/eng/tox/create_package_and_install.py -d {envtmpdir} -p {tox_root} -w {envtmpdir}
74-
python -m pip freeze
7591
pytest {[pytest]default_args} {posargs} {tox_root}
7692
python {repository_root}/eng/tox/run_coverage.py -t {tox_root} -r {repository_root}
7793

@@ -91,8 +107,8 @@ setenv =
91107
deps =
92108
-rdev_requirements.txt
93109
commands =
94-
python -m pip install pylint=={[testenv:pylint]pylint_version}
95-
python -m pip install azure-pylint-guidelines-checker==0.5.6 --index-url="https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-python/pypi/simple/"
110+
python -m {[tox]pip_command} install pylint=={[testenv:pylint]pylint_version}
111+
python -m {[tox]pip_command} install azure-pylint-guidelines-checker==0.5.6 --index-url="https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-python/pypi/simple/"
96112
python {repository_root}/eng/tox/create_package_and_install.py \
97113
-d {envtmpdir}/dist \
98114
-p {tox_root} \
@@ -116,8 +132,8 @@ deps =
116132
-rdev_requirements.txt
117133
PyGitHub>=1.59.0
118134
commands =
119-
python -m pip install pylint=={[testenv:next-pylint]pylint_version}
120-
python -m pip install azure-pylint-guidelines-checker==0.5.6 --index-url="https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-python/pypi/simple/"
135+
python -m {[tox]pip_command} install pylint=={[testenv:next-pylint]pylint_version}
136+
python -m {[tox]pip_command} install azure-pylint-guidelines-checker==0.5.6 --index-url="https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-python/pypi/simple/"
121137
python {repository_root}/eng/tox/create_package_and_install.py \
122138
-d {envtmpdir}/dist \
123139
-p {tox_root} \
@@ -272,10 +288,10 @@ setenv =
272288
{[testenv]setenv}
273289
PROXY_URL=http://localhost:5004
274290
commands =
275-
- python -m pip uninstall aiohttp --yes
291+
- python -m {[tox]pip_command} uninstall aiohttp --yes
276292
python {repository_root}/eng/tox/create_package_and_install.py -d {envtmpdir} -p {tox_root} -w {envtmpdir}
277293
python {repository_root}/eng/tox/try_import.py aiohttp -p {tox_root}
278-
python -m pip freeze
294+
python -m {[tox]pip_command} freeze
279295
pytest {[pytest]default_args} --ignore-glob='*async*.py' {posargs} --no-cov {tox_root}
280296

281297

@@ -297,7 +313,7 @@ commands =
297313
-p {tox_root} \
298314
-w {envtmpdir} \
299315
--package-type sdist
300-
python -m pip freeze
316+
python -m {[tox]pip_command} freeze
301317
pytest {posargs} --no-cov {[pytest]ignore_args} {tox_root}
302318

303319

@@ -380,12 +396,12 @@ setenv =
380396
deps =
381397
{[packaging]pkgs}
382398
commands =
383-
python -m pip install {repository_root}/tools/azure-sdk-tools --no-deps
399+
python -m {[tox]pip_command} install {repository_root}/tools/azure-sdk-tools --no-deps
384400
python {repository_root}/eng/tox/create_package_and_install.py \
385401
-d {envtmpdir} \
386402
-p {tox_root} \
387403
-w {envtmpdir}
388-
python -m pip freeze
404+
python -m {[tox]pip_command} freeze
389405
python {repository_root}/eng/tox/import_all.py -t {tox_root}
390406

391407

@@ -399,7 +415,7 @@ setenv =
399415
deps =
400416
{[packaging]pkgs}
401417
commands =
402-
python -m pip install {repository_root}/tools/azure-sdk-tools --no-deps
418+
python -m {[tox]pip_command} install {repository_root}/tools/azure-sdk-tools --no-deps
403419
python {repository_root}/eng/tox/create_package_and_install.py -d {envtmpdir} -p {tox_root} --skip-install True
404420
python {repository_root}/eng/tox/verify_whl.py -d {envtmpdir} -t {tox_root}
405421

@@ -409,12 +425,11 @@ description=Verify directories included in sdist and contents in manifest file.
409425
skipsdist = true
410426
skip_install = true
411427
setenv =
412-
{[testenv]setenv}
413-
PROXY_URL=http://localhost:5010
428+
{[testenv]setenv} PROXY_URL=http://localhost:5010
414429
deps =
415430
{[packaging]pkgs}
416431
commands =
417-
python -m pip install {repository_root}/tools/azure-sdk-tools --no-deps
432+
python -m {[tox]pip_command} install {repository_root}/tools/azure-sdk-tools --no-deps
418433
python {tox_root}/setup.py --q sdist -d {envtmpdir}
419434
python {repository_root}/eng/tox/verify_sdist.py -d {envtmpdir} -t {tox_root}
420435

@@ -435,7 +450,7 @@ commands =
435450
commands =
436451
python {repository_root}/eng/tox/install_depend_packages.py -t {tox_root} -d {env:DEPENDENCY_TYPE:} -w {envtmpdir}
437452
python {repository_root}/eng/tox/create_package_and_install.py -d {envtmpdir} -p {tox_root} -w {envtmpdir} --pre-download-disabled
438-
python -m pip freeze
453+
python -m {[tox]pip_command} freeze
439454
python {repository_root}/eng/tox/verify_installed_packages.py --packages-file {envtmpdir}/packages.txt
440455
pytest {[pytest]default_args} {posargs} --no-cov {tox_root}
441456

@@ -484,8 +499,8 @@ deps =
484499
{[base]deps}
485500
commands =
486501
# install API stub generator
487-
python -m pip install -r {repository_root}/eng/apiview_reqs.txt --index-url="https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-python/pypi/simple/"
488-
python -m pip freeze
502+
python -m {[tox]pip_command} install -r {repository_root}/eng/apiview_reqs.txt --index-url="https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-python/pypi/simple/"
503+
python -m {[tox]pip_command} freeze
489504
python {repository_root}/eng/tox/run_apistubgen.py -t {tox_root} -w {envtmpdir} {posargs}
490505

491506

@@ -501,7 +516,7 @@ setenv =
501516
deps =
502517
{[base]deps}
503518
commands =
504-
python -m pip freeze
519+
python -m {[tox]pip_command} freeze
505520
python {repository_root}/eng/tox/run_bandit.py -t {tox_root}
506521

507522

@@ -518,7 +533,7 @@ deps =
518533
{[base]deps}
519534
subprocess32; python_version < '3.5'
520535
commands =
521-
python -m pip freeze
536+
python -m {[tox]pip_command} freeze
522537
python {repository_root}/scripts/devops_tasks/test_run_samples.py -t {tox_root}
523538

524539

@@ -584,5 +599,5 @@ setenv =
584599
{[testenv]setenv}
585600
PROXY_URL=http://localhost:5018
586601
commands =
587-
{envbindir}/python -m pip install {toxinidir}/../../../tools/azure-sdk-tools[build]
602+
{envbindir}/python -m {[tox]pip_command} install {toxinidir}/../../../tools/azure-sdk-tools[build]
588603
python {repository_root}/eng/tox/run_optional.py -t {toxinidir} --temp={envtmpdir} {posargs}

tools/azure-sdk-tools/ci_tools/functions.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -927,3 +927,20 @@ def verify_package_classifiers(package_name: str, package_version: str, package_
927927
if num < 5:
928928
return False, f"{package_name} has version {package_version} and is a GA release, but had development status '{c}'. Expecting a development classifier that is equal or greater than 'Development Status :: 5 - Production/Stable'."
929929
return True, None
930+
931+
def get_pip_command(python_exe: Optional[str] = None) -> List[str]:
932+
"""
933+
Determine whether to use 'uv pip' or regular 'pip' based on environment.
934+
935+
:param str python_exe: The Python executable to use (if not using the default).
936+
:return: List of command arguments for pip.
937+
:rtype: List[str]
938+
939+
"""
940+
# Check TOX_PIP_IMPL environment variable (aligns with tox.ini configuration)
941+
pip_impl = os.environ.get('TOX_PIP_IMPL', 'pip').lower()
942+
943+
if pip_impl == 'uv':
944+
return ["uv", "pip"]
945+
else:
946+
return [python_exe if python_exe else sys.executable, "-m", "pip"]

tools/azure-sdk-tools/ci_tools/scenario/generation.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@
1414
discover_prebuilt_package,
1515
pip_install,
1616
pip_uninstall,
17+
get_pip_command,
1718
)
1819
from ci_tools.build import cleanup_build_artifacts, create_package
1920
from ci_tools.parsing import ParsedSetup, parse_require
2021
from ci_tools.functions import get_package_from_repo_or_folder, find_whl, get_pip_list_output, pytest
2122
from .managed_virtual_env import ManagedVirtualEnv
2223

23-
2424
def prepare_environment(package_folder: str, venv_directory: str, env_name: str) -> str:
2525
"""
2626
Empties the venv_directory directory and creates a virtual environment within. Returns the path to the new python executable.
@@ -112,10 +112,8 @@ def create_package_and_install(
112112
"Found {} azure requirement(s): {}".format(len(azure_requirements), azure_requirements)
113113
)
114114

115-
download_command = [
116-
python_exe,
117-
"-m",
118-
"pip",
115+
pip_cmd = get_pip_command(python_exe)
116+
download_command = pip_cmd + [
119117
"download",
120118
"-d",
121119
tmp_dl_folder,
@@ -174,7 +172,8 @@ def create_package_and_install(
174172
for package_name in non_present_reqs
175173
]
176174

177-
commands = [python_exe, "-m", "pip", "install", built_pkg_path]
175+
pip_cmd = get_pip_command(python_exe)
176+
commands = pip_cmd + ["install", built_pkg_path]
178177
commands.extend(additional_downloaded_reqs)
179178
commands.extend(commands_options)
180179

0 commit comments

Comments
 (0)