Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .chronus/changes/copilot-fix-3115-2025-6-1-3-6-11.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: internal
packages:
- "@autorest/python"
---

Regular update
8 changes: 8 additions & 0 deletions .chronus/changes/copilot-fix-3115-2025-7-01-02-45-12.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: feature
packages:
- "@azure-tools/typespec-python"
---

[typespec-python] Add support for uv package manager alongside pip
2 changes: 1 addition & 1 deletion packages/autorest.python/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
},
"homepage": "https://github.com/Azure/autorest.python/blob/main/README.md",
"dependencies": {
"@typespec/http-client-python": "~0.12.4",
"@typespec/http-client-python": "0.13.0-dev.2",
"@autorest/system-requirements": "~1.0.2",
"fs-extra": "~11.2.0",
"tsx": "~4.19.1"
Expand Down
2 changes: 1 addition & 1 deletion packages/typespec-python/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"js-yaml": "~4.1.0",
"semver": "~7.6.2",
"tsx": "~4.19.1",
"@typespec/http-client-python": "~0.12.4",
"@typespec/http-client-python": "0.13.0-dev.2",
"fs-extra": "~11.2.0"
},
"devDependencies": {
Expand Down
30 changes: 14 additions & 16 deletions packages/typespec-python/scripts/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,35 @@
raise Exception("Autorest for Python extension requires Python 3.9 at least")

try:
import pip
except ImportError:
raise Exception("Your Python installation doesn't have pip available")
from package_manager import detect_package_manager, PackageManagerNotFoundError

detect_package_manager() # Just check if we have a package manager
except (ImportError, ModuleNotFoundError, PackageManagerNotFoundError):
raise Exception("Your Python installation doesn't have a suitable package manager (pip or uv) available")

try:
import venv
except ImportError:
raise Exception("Your Python installation doesn't have venv available")


# Now we have pip and Py >= 3.9, go to work
# Now we have a package manager (uv or pip) and Py >= 3.9, go to work

from pathlib import Path

from venvtools import ExtendedEnvBuilder, python_run

_ROOT_DIR = Path(__file__).parent.parent


def main():
venv_path = _ROOT_DIR / "venv"
if venv_path.exists():
env_builder = venv.EnvBuilder(with_pip=True)
venv_context = env_builder.ensure_directories(venv_path)
else:
env_builder = ExtendedEnvBuilder(with_pip=True, upgrade_deps=True)
env_builder.create(venv_path)
venv_context = env_builder.context

python_run(venv_context, "pip", ["install", "-U", "pip"])
python_run(venv_context, "pip", ["install", "-U", "black"])

# Create virtual environment using package manager abstraction
from package_manager import create_venv_with_package_manager, install_packages

venv_context = create_venv_with_package_manager(venv_path)

# Install required packages - install_packages handles package manager logic
install_packages(["-U", "black"], venv_context)


if __name__ == "__main__":
Expand Down
141 changes: 141 additions & 0 deletions packages/typespec-python/scripts/package_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#!/usr/bin/env python

# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------
"""Package manager utilities for detecting and using pip or uv."""

import subprocess
import sys
import venv
from pathlib import Path
from venvtools import ExtendedEnvBuilder


class PackageManagerNotFoundError(Exception):
"""Raised when no suitable package manager is found."""

pass


def _check_command_available(command: str) -> bool:
"""Check if a command is available in the environment."""
try:
subprocess.run([command, "--version"], capture_output=True, check=True)
return True
except (subprocess.CalledProcessError, FileNotFoundError):
return False


def detect_package_manager() -> str:
"""Detect the best available package manager.

Returns:
str: The package manager command ('uv' or 'pip')

Raises:
PackageManagerNotFoundError: If no suitable package manager is found
"""
# Check for uv first since it's more modern and faster
if _check_command_available("uv"):
return "uv"

# Fall back to pip
if _check_command_available("pip"):
return "pip"

# As a last resort, try using python -m pip
try:
subprocess.run([sys.executable, "-m", "pip", "--version"], capture_output=True, check=True)
return "python -m pip"
except (subprocess.CalledProcessError, FileNotFoundError):
pass

raise PackageManagerNotFoundError("No suitable package manager found. Please install either uv or pip.")


def get_install_command(package_manager: str, venv_context=None) -> list:
"""Get the install command for the given package manager.

Args:
package_manager: The package manager command ('uv', 'pip', or 'python -m pip')
venv_context: The virtual environment context (optional, used for pip)

Returns:
list: The base install command as a list
"""
if package_manager == "uv":
cmd = ["uv", "pip", "install"]
if venv_context:
cmd.extend(["--python", venv_context.env_exe])
return cmd
elif package_manager == "pip":
if venv_context:
return [venv_context.env_exe, "-m", "pip", "install"]
else:
return ["pip", "install"]
elif package_manager == "python -m pip":
if venv_context:
return [venv_context.env_exe, "-m", "pip", "install"]
else:
return [sys.executable, "-m", "pip", "install"]
else:
raise ValueError(f"Unknown package manager: {package_manager}")


def install_packages(packages: list, venv_context=None, package_manager: str = None) -> None:
"""Install packages using the available package manager.

Args:
packages: List of packages to install
venv_context: Virtual environment context (optional)
package_manager: Package manager to use (auto-detected if None)
"""
if package_manager is None:
package_manager = detect_package_manager()

install_cmd = get_install_command(package_manager, venv_context)

try:
subprocess.check_call(install_cmd + packages)
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Failed to install packages with {package_manager}: {e}")


def create_venv_with_package_manager(venv_path):
"""Create virtual environment using the best available package manager.

Args:
venv_path: Path where to create the virtual environment

Returns:
venv_context: Virtual environment context object
"""
package_manager = detect_package_manager()

if package_manager == "uv":
# Use uv to create and manage the virtual environment
if not venv_path.exists():
subprocess.check_call(["uv", "venv", str(venv_path)])

# Create a mock venv_context for compatibility
class MockVenvContext:
def __init__(self, venv_path):
self.env_exe = (
str(venv_path / "bin" / "python")
if sys.platform != "win32"
else str(venv_path / "Scripts" / "python.exe")
)

return MockVenvContext(venv_path)
else:
# Use standard venv for pip
if venv_path.exists():
env_builder = venv.EnvBuilder(with_pip=True)
return env_builder.ensure_directories(venv_path)
else:
env_builder = ExtendedEnvBuilder(with_pip=True, upgrade_deps=True)
env_builder.create(venv_path)
return env_builder.context
12 changes: 4 additions & 8 deletions packages/typespec-python/scripts/prepare.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,12 @@
# license information.
# --------------------------------------------------------------------------
import sys
import os
import argparse

if not sys.version_info >= (3, 9, 0):
raise Exception("Autorest for Python extension requires Python 3.9 at least")

from pathlib import Path
import venv

from venvtools import python_run
from package_manager import create_venv_with_package_manager, install_packages

_ROOT_DIR = Path(__file__).parent.parent

Expand All @@ -26,10 +22,10 @@ def main():

assert venv_preexists # Otherwise install was not done

env_builder = venv.EnvBuilder(with_pip=True)
venv_context = env_builder.ensure_directories(venv_path)
venv_context = create_venv_with_package_manager(venv_path)

try:
python_run(venv_context, "pip", ["install", "-r", f"{_ROOT_DIR}/dev_requirements.txt"])
install_packages(["-r", f"{_ROOT_DIR}/dev_requirements.txt"], venv_context)
except FileNotFoundError as e:
raise ValueError(e.filename)

Expand Down
7 changes: 3 additions & 4 deletions packages/typespec-python/scripts/run_tsp.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
# license information.
# --------------------------------------------------------------------------
import sys
import venv
import logging
from pathlib import Path
from pygen import preprocess, codegen
from pygen.utils import parse_args
from package_manager import create_venv_with_package_manager

_ROOT_DIR = Path(__file__).parent.parent

Expand All @@ -20,14 +20,13 @@

assert venv_preexists # Otherwise install was not done

env_builder = venv.EnvBuilder(with_pip=True)
venv_context = env_builder.ensure_directories(venv_path)
venv_context = create_venv_with_package_manager(venv_path)

if "--debug" in sys.argv or "--debug=true" in sys.argv:
try:
import debugpy # pylint: disable=import-outside-toplevel
except ImportError:
raise SystemExit("Please pip install ptvsd in order to use VSCode debugging")
raise SystemExit("Please install ptvsd in order to use VSCode debugging")

# 5678 is the default attach port in the VS Code debug configurations
debugpy.listen(("localhost", 5678))
Expand Down
39 changes: 1 addition & 38 deletions packages/typespec-python/scripts/venvtools.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------
from contextlib import contextmanager
import tempfile
import subprocess
import venv
import sys
Expand All @@ -28,45 +26,10 @@ def ensure_directories(self, env_dir):
return self.context


def create(
env_dir, system_site_packages=False, clear=False, symlinks=False, with_pip=False, prompt=None, upgrade_deps=False
):
"""Create a virtual environment in a directory."""
builder = ExtendedEnvBuilder(
system_site_packages=system_site_packages,
clear=clear,
symlinks=symlinks,
with_pip=with_pip,
prompt=prompt,
upgrade_deps=upgrade_deps,
)
builder.create(env_dir)
return builder.context


@contextmanager
def create_venv_with_package(packages):
"""Create a venv with these packages in a temp dir and yield the env.

packages should be an iterable of pip version instructions (e.g. package~=1.2.3)
"""
with tempfile.TemporaryDirectory() as tempdir:
myenv = create(tempdir, with_pip=True, upgrade_deps=True)
pip_call = [
myenv.env_exe,
"-m",
"pip",
"install",
]
subprocess.check_call(pip_call + ["-U", "pip"])
if packages:
subprocess.check_call(pip_call + packages)
yield myenv


def python_run(venv_context, module, command=None, *, additional_dir="."):
try:
cmd_line = [venv_context.env_exe, "-m", module] + (command if command else [])

print("Executing: {}".format(" ".join(cmd_line)))
subprocess.run(
cmd_line,
Expand Down
14 changes: 7 additions & 7 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading