diff options
author | S. Solomon Darnell | 2025-03-28 21:52:21 -0500 |
---|---|---|
committer | S. Solomon Darnell | 2025-03-28 21:52:21 -0500 |
commit | 4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch) | |
tree | ee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/azure/ai/ml/_local_endpoints/vscode_debug | |
parent | cc961e04ba734dd72309fb548a2f97d67d578813 (diff) | |
download | gn-ai-master.tar.gz |
Diffstat (limited to '.venv/lib/python3.12/site-packages/azure/ai/ml/_local_endpoints/vscode_debug')
4 files changed, 400 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/azure/ai/ml/_local_endpoints/vscode_debug/__init__.py b/.venv/lib/python3.12/site-packages/azure/ai/ml/_local_endpoints/vscode_debug/__init__.py new file mode 100644 index 00000000..d540fd20 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/azure/ai/ml/_local_endpoints/vscode_debug/__init__.py @@ -0,0 +1,3 @@ +# --------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# --------------------------------------------------------- diff --git a/.venv/lib/python3.12/site-packages/azure/ai/ml/_local_endpoints/vscode_debug/devcontainer_properties.py b/.venv/lib/python3.12/site-packages/azure/ai/ml/_local_endpoints/vscode_debug/devcontainer_properties.py new file mode 100644 index 00000000..fa60d379 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/azure/ai/ml/_local_endpoints/vscode_debug/devcontainer_properties.py @@ -0,0 +1,156 @@ +# --------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# --------------------------------------------------------- +# +# This file contains devcontainer.json properties as Python classes. +# Reference: https://code.visualstudio.com/docs/remote/devcontainerjson-reference + + +from typing import Dict, Optional + +from azure.ai.ml.constants._endpoint import LocalEndpointConstants + + +class Image(object): + """Python object representation of devcontainer image property.""" + + def __init__(self, image: str): + self._image = image + + def to_dict(self) -> dict: + return {"image": self._image} + + +class Build(object): + """Python object representation of devcontainer build.dockerfile property.""" + + def __init__( + self, + dockerfile_path: str, + build_context: Optional[str] = None, + args: Optional[dict] = None, + target: Optional[str] = None, + ): + self._dockerfile_path = dockerfile_path + self._build_context = build_context + self._args = args + self._target = target + + def to_dict(self) -> dict: + build: Dict = { + "build": { + "dockerfile": self._dockerfile_path, + } + } + if self._build_context: + build["build"]["context"] = self._build_context + if self._args: + build["build"]["args"] = self._args + if self._target: + build["build"]["target"] = self._target + return build + + +class ContainerEnv(object): + """Python object representation of devcontainer containerEnv property.""" + + def __init__(self, environment_variables: dict): + self._environment_variables = environment_variables + + def to_dict(self) -> dict: + return {"containerEnv": self._environment_variables} + + +class Mounts(object): + """Python object representation of devcontainer mounts property.""" + + def __init__(self, mounts: list): + self._mounts = mounts + + def to_dict(self) -> dict: + return {"mounts": self._mounts} + + +class Name(object): + """Python object representation of devcontainer name property.""" + + def __init__(self, name: str): + self._name = name + + def to_dict(self) -> dict: + return {"name": self._name} + + +class ForwardPorts(object): + """Python object representation of devcontainer name property.""" + + def __init__(self, port: int): + self._port = port + + def to_dict(self) -> dict: + return {"forwardPorts": [self._port]} + + +class AppPort(object): + """Python object representation of devcontainer name property.""" + + def __init__(self, port: int): + self._port = port + + def to_dict(self) -> dict: + return {"appPort": [self._port]} + + +class RunArgs(object): + """Python object representation of devcontainer runArgs property.""" + + def __init__(self, name: Optional[str] = None, labels: Optional[list] = None): + labels = labels or [] + self._run_args = labels + if name: + self._run_args.append(f"--name={name}") + + def to_dict(self) -> dict: + return {"runArgs": self._run_args} + + +class OverrideCommand(object): + def __init__(self): + pass + + def to_dict(self) -> dict: + return {"overrideCommand": True} + + +class Extensions(object): + def __init__(self): + pass + + def to_dict(self) -> dict: + return {"extensions": ["ms-python.python", "ms-toolsai.vscode-ai-inference"]} + + +class Settings(object): + def __init__(self): + pass + + def to_dict(self) -> dict: + return { + "settings": { + "launch": { + "configurations": [ + { + "name": "Azure ML: Debug Local Endpoint", + "type": "python", + "request": "attach", + "listen": { + "host": "127.0.0.1", + "port": 0, + }, + "azuremlext": "local_inference_debug", + } + ] + }, + "python.defaultInterpreterPath": LocalEndpointConstants.CONDA_ENV_PYTHON_PATH, + } + } diff --git a/.venv/lib/python3.12/site-packages/azure/ai/ml/_local_endpoints/vscode_debug/devcontainer_resolver.py b/.venv/lib/python3.12/site-packages/azure/ai/ml/_local_endpoints/vscode_debug/devcontainer_resolver.py new file mode 100644 index 00000000..0c6a77dc --- /dev/null +++ b/.venv/lib/python3.12/site-packages/azure/ai/ml/_local_endpoints/vscode_debug/devcontainer_resolver.py @@ -0,0 +1,190 @@ +# --------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# --------------------------------------------------------- + + +import json +from pathlib import Path +from typing import Dict, List, Optional + +from azure.ai.ml._local_endpoints.utilities.wsl_utility import get_wsl_path, in_wsl +from azure.ai.ml._local_endpoints.vscode_debug.devcontainer_properties import ( + AppPort, + Build, + ContainerEnv, + Extensions, + ForwardPorts, + Image, + Mounts, + OverrideCommand, + RunArgs, + Settings, +) +from azure.ai.ml.constants._common import DefaultOpenEncoding +from azure.ai.ml.exceptions import ErrorCategory, ErrorTarget, ValidationException + + +class DevContainerResolver: + """DevContainerResolver class represents the collection of properties of a devcontainer.json. + + Reference: https://code.visualstudio.com/docs/remote/devcontainerjson-reference + """ + + def __init__( + self, + image: Optional[str] = None, + dockerfile_path: str = "../Dockerfile", + build_context: Optional[str] = None, + build_target: Optional[str] = None, + environment: Optional[dict] = None, + mounts: Optional[dict] = None, + labels: Optional[dict] = None, + port: int = 5001, + ): + """Resolves the devcontainer.json based on provided properties. + + :param image: name of local deployment + :type image: str + :param dockerfile_path: path to Dockerfile relative to devcontainer.json + :type dockerfile_path: str + :param build_context: build directory on user's local system + :type build_context: str + :param build_target: directory on user's local system where Dockerfile is located + :type build_target: str + :param environment: dictionary of docker environment variables to set in dev container + :type environment: dict + :param mounts: dictionary of volumes to mount to dev container + :type mounts: dict + :param labels: dictionary of labels to add to dev container + :type labels: dict + :param port: Port exposed in Docker image for AzureML service. + :type port: int + """ + if not (image or (build_context and dockerfile_path)): + msg = "Must provide image or build context for devcontainer.json" + raise ValidationException( + message=msg, + no_personal_data_message=msg, + target=ErrorTarget.LOCAL_ENDPOINT, + error_category=ErrorCategory.USER_ERROR, + ) + self._local_path: Optional[str] = None + self._properties: Optional[dict] = {} + + self._image: Optional[str] = image + self._dockerfile_path: str = dockerfile_path + self._build_context: Optional[str] = build_context + self._build_target: Optional[str] = build_target + self._environment: Optional[dict] = environment + self._mounts: list = _reformat_mounts(mounts) if mounts else mounts # type: ignore[assignment] + self._labels: list = _reformat_labels(labels) if labels else labels # type: ignore[assignment] + self._port = port + self._construct() + + @property + def local_path(self) -> Optional[str]: + """Returns the local path of the devcontainer.json. + + :return: str + """ + return self._local_path + + def _construct(self) -> None: + """Constructs the devcontainer properties based on attributes.""" + if self._image: + self._properties = Image(image=self._image).to_dict() + elif self._dockerfile_path and self._build_context: + self._properties = Build( + dockerfile_path=self._dockerfile_path, + build_context=self._build_context, + target=self._build_target, + ).to_dict() + + if self._properties is not None: + self._properties.update(OverrideCommand().to_dict()) + self._properties.update(Extensions().to_dict()) + self._properties.update(Settings().to_dict()) + + if self._environment: + self._properties.update(ContainerEnv(environment_variables=self._environment).to_dict()) + if self._mounts: + self._properties.update(Mounts(mounts=self._mounts).to_dict()) + if self._labels: + self._properties.update(RunArgs(labels=self._labels).to_dict()) + if self._port: + self._properties.update(AppPort(port=self._port).to_dict()) + self._properties.update(ForwardPorts(port=self._port).to_dict()) + + def write_file(self, directory_path: str) -> None: + """Writes this devcontainer.json to provided directory. + + :param directory_path: absolute path of local directory to write devcontainer.json. + :type directory_path: str + """ + self._local_path = get_wsl_path(directory_path) if in_wsl() else directory_path + + file_path = _get_devcontainer_file_path(directory_path=directory_path) + with open(file_path, "w", encoding=DefaultOpenEncoding.WRITE) as f: + f.write(f"{json.dumps(self._properties, indent=4)}\n") + + +def _reformat_mounts(mounts: Dict[str, Dict[str, Dict[str, str]]]) -> List[str]: + """Reformat mounts from Docker format to DevContainer format. + + :param mounts: Dictionary with mount information for Docker container. For example: + .. code-block:: python + + { + "<unique mount key>": { + "<local_source>": { + "<mount type i.e. bind>": "<container_dest>" + } + } + } + + :type mounts: dict + :return: + ["source=${localWorkspaceFolder}/app-scripts, target=/usr/local/share/app-scripts,type=bind,consistency=cached"] + :rtype: List[str] + """ + devcontainer_mounts = [] + for mount_dict in mounts.values(): + for source, dest in mount_dict.items(): + for mount_type, container_dest in dest.items(): + devcontainer_mounts.append(f"source={source},target={container_dest},type={mount_type}") + return devcontainer_mounts + + +def _reformat_labels(labels: Dict[str, str]) -> List[str]: + """Reformat labels from Docker format to DevContainer format. + + :param labels: Dictionary with label information for Docker container. For example: + .. code-block:: python + + { + "key": "value", + "key1": "value1" + } + + :type labels: Dict[str, str] + :return: ["--label=key=value", "--label=key1=value1"] + :rtype: List[str] + """ + devcontainer_labels = [] + for key, value in labels.items(): + devcontainer_labels.append(f"--label={key}={value}") + return devcontainer_labels + + +def _get_devcontainer_file_path(directory_path: str) -> str: + """Returns the path of the devcontainer in relation to provided directory path. + + :param directory_path: absolute path of local directory to write devcontainer.json. + :type directory_path: str + :return: Absolute path to the devcontainer + :rtype: str + """ + devcontainer_path = Path(directory_path, ".devcontainer") + devcontainer_path.mkdir(parents=True, exist_ok=True) + file_path = str(Path(devcontainer_path, "devcontainer.json").resolve()) + return file_path diff --git a/.venv/lib/python3.12/site-packages/azure/ai/ml/_local_endpoints/vscode_debug/vscode_client.py b/.venv/lib/python3.12/site-packages/azure/ai/ml/_local_endpoints/vscode_debug/vscode_client.py new file mode 100644 index 00000000..b2381bbf --- /dev/null +++ b/.venv/lib/python3.12/site-packages/azure/ai/ml/_local_endpoints/vscode_debug/vscode_client.py @@ -0,0 +1,51 @@ +# --------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# --------------------------------------------------------- +import binascii +import re + +from azure.ai.ml._local_endpoints.utilities.commandline_utility import run_cli_command +from azure.ai.ml._local_endpoints.vscode_debug.devcontainer_resolver import DevContainerResolver +from azure.ai.ml.exceptions import VSCodeCommandNotFound + + +class VSCodeClient(object): + # pylint: disable=client-method-has-more-than-5-positional-arguments + def create_dev_container_json( + self, + azureml_container, # pylint: disable=unused-argument + endpoint_name: str, # pylint: disable=unused-argument + deployment_name: str, # pylint: disable=unused-argument + build_directory: str, + image_name: str, + environment: dict, + volumes: list, + labels: dict, + ) -> str: + devcontainer = DevContainerResolver( + image=image_name, + environment=environment, + mounts=volumes, # type: ignore[arg-type] + labels=labels, + ) + devcontainer.write_file(build_directory) + return str(devcontainer.local_path) + + def invoke_dev_container(self, devcontainer_path: str, app_path: str) -> None: + hex_encoded_devcontainer_path = _encode_hex(devcontainer_path) + command = [ + "code", + "--folder-uri", + f"vscode-remote://dev-container+{hex_encoded_devcontainer_path}{app_path}", + ] + try: + run_cli_command(command) + except Exception as e: + # pylint: disable=no-member + output = e.output.decode(encoding="UTF-8") # type: ignore[attr-defined] + raise VSCodeCommandNotFound(output) from e + + +def _encode_hex(path: str): + vscode_path = re.sub("\\s+", "", path) + return binascii.hexlify(vscode_path.encode()).decode("ascii") |