diff --git a/sdk/ai/azure-ai-projects/api.md b/sdk/ai/azure-ai-projects/api.md index e9a9ac4fc0e5..0bbcf1367646 100644 --- a/sdk/ai/azure-ai-projects/api.md +++ b/sdk/ai/azure-ai-projects/api.md @@ -252,7 +252,7 @@ namespace azure.ai.projects.aio.operations ) -> None: ... @distributed_trace_async - async def download_code( + async def download_code_as_bytes( self, agent_name: str, *, @@ -261,7 +261,18 @@ namespace azure.ai.projects.aio.operations ) -> AsyncIterator[bytes]: ... @distributed_trace_async - async def download_session_file( + async def download_code_to_path( + self, + agent_name: str, + *, + agent_version: Optional[str] = ..., + file_path: Union[str, PathLike[str]], + overwrite: bool = False, + **kwargs: Any + ) -> str: ... + + @distributed_trace_async + async def download_session_file_as_bytes( self, agent_name: str, agent_session_id: str, @@ -271,12 +282,13 @@ namespace azure.ai.projects.aio.operations ) -> AsyncIterator[bytes]: ... @distributed_trace_async - async def download_session_file_to_disk( + async def download_session_file_to_path( self, agent_name: str, session_id: str, *, file_path: Union[str, PathLike[str]], + overwrite: bool = False, remote_path: str, **kwargs: Any ) -> None: ... @@ -9598,7 +9610,7 @@ namespace azure.ai.projects.operations ) -> None: ... @distributed_trace - def download_code( + def download_code_as_bytes( self, agent_name: str, *, @@ -9607,7 +9619,18 @@ namespace azure.ai.projects.operations ) -> Iterator[bytes]: ... @distributed_trace - def download_session_file( + def download_code_to_path( + self, + agent_name: str, + *, + agent_version: Optional[str] = ..., + file_path: Union[str, PathLike[str]], + overwrite: bool = False, + **kwargs: Any + ) -> str: ... + + @distributed_trace + def download_session_file_as_bytes( self, agent_name: str, agent_session_id: str, @@ -9617,12 +9640,13 @@ namespace azure.ai.projects.operations ) -> Iterator[bytes]: ... @distributed_trace - def download_session_file_to_disk( + def download_session_file_to_path( self, agent_name: str, session_id: str, *, file_path: Union[str, PathLike[str]], + overwrite: bool = False, remote_path: str, **kwargs: Any ) -> None: ... diff --git a/sdk/ai/azure-ai-projects/api.metadata.yml b/sdk/ai/azure-ai-projects/api.metadata.yml index b765bbe1d618..81f4c11fdf83 100644 --- a/sdk/ai/azure-ai-projects/api.metadata.yml +++ b/sdk/ai/azure-ai-projects/api.metadata.yml @@ -1,3 +1,3 @@ -apiMdSha256: aaf7da6cfe754ac521a35dde3f23b00242c300533c89e6e1001e434a63bb8e4b +apiMdSha256: 20fc951d6f2be65beff46e91c99cfeaa3758ee6b8ca0924997b8fc196d7c54f4 parserVersion: 0.3.28 pythonVersion: 3.14.3 diff --git a/sdk/ai/azure-ai-projects/apiview-properties.json b/sdk/ai/azure-ai-projects/apiview-properties.json index 9b301fb61240..0436a3415e7e 100644 --- a/sdk/ai/azure-ai-projects/apiview-properties.json +++ b/sdk/ai/azure-ai-projects/apiview-properties.json @@ -463,8 +463,8 @@ "azure.ai.projects.aio.operations.AgentsOperations.update_details": "Azure.AI.Projects.Agents.patchAgentObject", "azure.ai.projects.operations.AgentsOperations.create_version_from_code": "Azure.AI.Projects.Agents.createAgentVersionFromCode", "azure.ai.projects.aio.operations.AgentsOperations.create_version_from_code": "Azure.AI.Projects.Agents.createAgentVersionFromCode", - "azure.ai.projects.operations.AgentsOperations.download_code": "Azure.AI.Projects.Agents.downloadAgentCode", - "azure.ai.projects.aio.operations.AgentsOperations.download_code": "Azure.AI.Projects.Agents.downloadAgentCode", + "azure.ai.projects.operations.AgentsOperations.download_code_as_bytes": "Azure.AI.Projects.Agents.downloadAgentCode", + "azure.ai.projects.aio.operations.AgentsOperations.download_code_as_bytes": "Azure.AI.Projects.Agents.downloadAgentCode", "azure.ai.projects.operations.AgentsOperations.enable": "Azure.AI.Projects.Agents.enableAgent", "azure.ai.projects.aio.operations.AgentsOperations.enable": "Azure.AI.Projects.Agents.enableAgent", "azure.ai.projects.operations.AgentsOperations.disable": "Azure.AI.Projects.Agents.disableAgent", @@ -481,8 +481,8 @@ "azure.ai.projects.aio.operations.AgentsOperations.list_sessions": "Azure.AI.Projects.Agents.listSessions", "azure.ai.projects.operations.AgentsOperations.get_session_log_stream": "Azure.AI.Projects.Agents.getSessionLogStream", "azure.ai.projects.aio.operations.AgentsOperations.get_session_log_stream": "Azure.AI.Projects.Agents.getSessionLogStream", - "azure.ai.projects.operations.AgentsOperations.download_session_file": "Azure.AI.Projects.AgentSessionFiles.downloadSessionFile", - "azure.ai.projects.aio.operations.AgentsOperations.download_session_file": "Azure.AI.Projects.AgentSessionFiles.downloadSessionFile", + "azure.ai.projects.operations.AgentsOperations.download_session_file_as_bytes": "Azure.AI.Projects.AgentSessionFiles.downloadSessionFile", + "azure.ai.projects.aio.operations.AgentsOperations.download_session_file_as_bytes": "Azure.AI.Projects.AgentSessionFiles.downloadSessionFile", "azure.ai.projects.operations.AgentsOperations.list_session_files": "Azure.AI.Projects.AgentSessionFiles.listSessionFiles", "azure.ai.projects.aio.operations.AgentsOperations.list_session_files": "Azure.AI.Projects.AgentSessionFiles.listSessionFiles", "azure.ai.projects.operations.AgentsOperations.delete_session_file": "Azure.AI.Projects.AgentSessionFiles.deleteSessionFile", @@ -542,5 +542,5 @@ "azure.ai.projects.operations.ToolboxesOperations.delete_version": "Azure.AI.Projects.Toolboxes.deleteToolboxVersion", "azure.ai.projects.aio.operations.ToolboxesOperations.delete_version": "Azure.AI.Projects.Toolboxes.deleteToolboxVersion" }, - "CrossLanguageVersion": "5833758ff7e9" + "CrossLanguageVersion": "3688b3de2e8e" } \ No newline at end of file diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py index a33cf674b10d..31d74e1cf613 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py @@ -46,8 +46,8 @@ build_agents_delete_session_request, build_agents_delete_version_request, build_agents_disable_request, - build_agents_download_code_request, - build_agents_download_session_file_request, + build_agents_download_code_as_bytes_request, + build_agents_download_session_file_as_bytes_request, build_agents_enable_request, build_agents_get_request, build_agents_get_session_log_stream_request, @@ -1462,7 +1462,7 @@ async def create_version_from_code( return deserialized # type: ignore @distributed_trace_async - async def download_code( + async def download_code_as_bytes( self, agent_name: str, *, agent_version: Optional[str] = None, **kwargs: Any ) -> AsyncIterator[bytes]: """Download agent code. @@ -1498,7 +1498,7 @@ async def download_code( cls: ClsType[AsyncIterator[bytes]] = kwargs.pop("cls", None) - _request = build_agents_download_code_request( + _request = build_agents_download_code_as_bytes_request( agent_name=agent_name, agent_version=agent_version, api_version=self._config.api_version, @@ -2299,7 +2299,7 @@ async def _upload_session_file( return deserialized # type: ignore @distributed_trace_async - async def download_session_file( + async def download_session_file_as_bytes( self, agent_name: str, agent_session_id: str, *, remote_path: str, **kwargs: Any ) -> AsyncIterator[bytes]: """Download a session file. @@ -2331,7 +2331,7 @@ async def download_session_file( cls: ClsType[AsyncIterator[bytes]] = kwargs.pop("cls", None) - _request = build_agents_download_session_file_request( + _request = build_agents_download_session_file_as_bytes_request( agent_name=agent_name, agent_session_id=agent_session_id, remote_path=remote_path, diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py index 188406471868..41d4362d8fda 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py @@ -8,6 +8,7 @@ Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ +import hashlib import os from pathlib import Path from typing import Union, Optional, Any, IO, overload @@ -311,12 +312,13 @@ async def upload_session_file( # type: ignore[override] return await super()._upload_session_file(agent_name, session_id, content, remote_path=remote_path, **kwargs) @distributed_trace_async - async def download_session_file_to_disk( + async def download_session_file_to_path( self, agent_name: str, session_id: str, *, file_path: Union[str, "os.PathLike[str]"], + overwrite: bool = False, remote_path: str, **kwargs: Any, ) -> None: @@ -331,21 +333,28 @@ async def download_session_file_to_disk( :type session_id: str :keyword file_path: The full path to the local file where the content should be written. Required. :paramtype file_path: str or os.PathLike[str] + :keyword overwrite: If True, overwrite the local file if it already exists. If False (default), + raise FileExistsError when the file already exists. + :paramtype overwrite: bool :keyword remote_path: The file path to download from the sandbox, relative to the session home directory. Required. :paramtype remote_path: str :return: None :rtype: None :raises ~azure.core.exceptions.HttpResponseError: + :raises FileExistsError: If *file_path* already exists and *overwrite* is False. :raises ValueError: If *file_path* points to a directory. :raises OSError: If the file cannot be written. """ p = Path(file_path) - if p.exists() and p.is_dir(): - raise ValueError(f"Provide a valid file path, not a folder path `{file_path}`.") + if p.exists(): + if p.is_dir(): + raise ValueError(f"Provide a valid file path, not a folder path `{file_path}`.") + if not overwrite: + raise FileExistsError(f"The file `{file_path}` already exists. Set overwrite=True to replace it.") # Download the file content using the existing method - content_iterator = await self.download_session_file( + content_iterator = await self.download_session_file_as_bytes( agent_name=agent_name, agent_session_id=session_id, remote_path=remote_path, @@ -356,3 +365,60 @@ async def download_session_file_to_disk( with open(file_path, "wb") as f: async for chunk in content_iterator: f.write(chunk) + + @distributed_trace_async + async def download_code_to_path( + self, + agent_name: str, + *, + file_path: Union[str, "os.PathLike[str]"], + overwrite: bool = False, + agent_version: Optional[str] = None, + **kwargs: Any, + ) -> str: + """Download agent code directly to disk. + + Downloads the code zip for a code-based hosted agent and writes it to a local file. + + If ``agent_version`` is supplied, downloads that version's code zip; otherwise + downloads the latest version's code zip. + + :param agent_name: The name of the agent. Required. + :type agent_name: str + :keyword file_path: The full path to the local file where the code zip should be written. Required. + :paramtype file_path: str or os.PathLike[str] + :keyword overwrite: If True, overwrite the local file if it already exists. If False (default), + raise FileExistsError when the file already exists. + :paramtype overwrite: bool + :keyword agent_version: The version of the agent whose code zip should be downloaded. + If omitted, the latest version's code zip is downloaded. Default value is None. + :paramtype agent_version: str + :return: The SHA-256 hex digest of the downloaded file. + :rtype: str + :raises ~azure.core.exceptions.HttpResponseError: + :raises FileExistsError: If *file_path* already exists and *overwrite* is False. + :raises ValueError: If *file_path* points to a directory. + :raises OSError: If the file cannot be written. + """ + p = Path(file_path) + if p.exists(): + if p.is_dir(): + raise ValueError(f"Provide a valid file path, not a folder path `{file_path}`.") + if not overwrite: + raise FileExistsError(f"The file `{file_path}` already exists. Set overwrite=True to replace it.") + + # Download the code content using the existing method + content_iterator = await self.download_code_as_bytes( + agent_name=agent_name, + agent_version=agent_version, + **kwargs, + ) + + # Write the content to disk and calculate SHA-256 + sha = hashlib.sha256() + with open(file_path, "wb") as f: + async for chunk in content_iterator: + f.write(chunk) + sha.update(chunk) + + return sha.hexdigest() diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_connections_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_connections_async.py index f32515aca2fe..792bb1631a82 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_connections_async.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_connections_async.py @@ -30,7 +30,7 @@ class ConnectionsOperations(ConnectionsOperationsGenerated): async def get(self, name: str, *, include_credentials: Optional[bool] = False, **kwargs: Any) -> Connection: """Get a connection by name. - :param name: The name of the resource. Required. + :param name: The name of the connection. Required. :type name: str :keyword include_credentials: Whether to include credentials in the response. Default is False. :paramtype include_credentials: bool @@ -38,18 +38,8 @@ async def get(self, name: str, *, include_credentials: Optional[bool] = False, * :rtype: ~azure.ai.projects.models.Connection :raises ~azure.core.exceptions.HttpResponseError: """ - if include_credentials: - connection = await super()._get_with_credentials(name, **kwargs) - if connection.type == ConnectionType.CUSTOM: - # Why do we do this? See comment in the sync version of this code (file _patch_connections.py). - setattr( - connection.credentials, - "credential_keys", - {k: v for k, v in connection.credentials.as_dict().items() if k != "type"}, - ) - - return connection + return await super()._get_with_credentials(name, **kwargs) return await super()._get(name, **kwargs) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py index 4b1f1e64b142..2fafe3cd426d 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py @@ -340,7 +340,7 @@ def build_agents_create_version_from_code_request( # pylint: disable=name-too-l return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) -def build_agents_download_code_request( +def build_agents_download_code_as_bytes_request( # pylint: disable=name-too-long agent_name: str, *, agent_version: Optional[str] = None, **kwargs: Any ) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) @@ -592,7 +592,7 @@ def build_agents_upload_session_file_request( return HttpRequest(method="PUT", url=_url, params=_params, headers=_headers, **kwargs) -def build_agents_download_session_file_request( # pylint: disable=name-too-long +def build_agents_download_session_file_as_bytes_request( # pylint: disable=name-too-long agent_name: str, agent_session_id: str, *, remote_path: str, **kwargs: Any ) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) @@ -4881,7 +4881,9 @@ def create_version_from_code( return deserialized # type: ignore @distributed_trace - def download_code(self, agent_name: str, *, agent_version: Optional[str] = None, **kwargs: Any) -> Iterator[bytes]: + def download_code_as_bytes( + self, agent_name: str, *, agent_version: Optional[str] = None, **kwargs: Any + ) -> Iterator[bytes]: """Download agent code. Downloads the code zip for a code-based hosted agent. @@ -4915,7 +4917,7 @@ def download_code(self, agent_name: str, *, agent_version: Optional[str] = None, cls: ClsType[Iterator[bytes]] = kwargs.pop("cls", None) - _request = build_agents_download_code_request( + _request = build_agents_download_code_as_bytes_request( agent_name=agent_name, agent_version=agent_version, api_version=self._config.api_version, @@ -5720,7 +5722,7 @@ def _upload_session_file( return deserialized # type: ignore @distributed_trace - def download_session_file( + def download_session_file_as_bytes( self, agent_name: str, agent_session_id: str, *, remote_path: str, **kwargs: Any ) -> Iterator[bytes]: """Download a session file. @@ -5752,7 +5754,7 @@ def download_session_file( cls: ClsType[Iterator[bytes]] = kwargs.pop("cls", None) - _request = build_agents_download_session_file_request( + _request = build_agents_download_session_file_as_bytes_request( agent_name=agent_name, agent_session_id=agent_session_id, remote_path=remote_path, diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py index 6765bd045e06..ac1c9f658c28 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py @@ -8,6 +8,7 @@ Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ +import hashlib import os from pathlib import Path from typing import Union, Optional, Any, IO, overload @@ -327,12 +328,13 @@ def upload_session_file( # type: ignore[override] return super()._upload_session_file(agent_name, session_id, content, remote_path=remote_path, **kwargs) @distributed_trace - def download_session_file_to_disk( + def download_session_file_to_path( self, agent_name: str, session_id: str, *, file_path: Union[str, "os.PathLike[str]"], + overwrite: bool = False, remote_path: str, **kwargs: Any, ) -> None: @@ -347,21 +349,28 @@ def download_session_file_to_disk( :type session_id: str :keyword file_path: The full path to the local file where the content should be written. Required. :paramtype file_path: str or os.PathLike[str] + :keyword overwrite: If True, overwrite the local file if it already exists. If False (default), + raise FileExistsError when the file already exists. + :paramtype overwrite: bool :keyword remote_path: The file path to download from the sandbox, relative to the session home directory. Required. :paramtype remote_path: str :return: None :rtype: None :raises ~azure.core.exceptions.HttpResponseError: + :raises FileExistsError: If *file_path* already exists and *overwrite* is False. :raises ValueError: If *file_path* points to a directory. :raises OSError: If the file cannot be written. """ p = Path(file_path) - if p.exists() and p.is_dir(): - raise ValueError(f"Provide a valid file path, not a folder path `{file_path}`.") + if p.exists(): + if p.is_dir(): + raise ValueError(f"Provide a valid file path, not a folder path `{file_path}`.") + if not overwrite: + raise FileExistsError(f"The file `{file_path}` already exists. Set overwrite=True to replace it.") # Download the file content using the existing method - content_iterator = self.download_session_file( + content_iterator = self.download_session_file_as_bytes( agent_name=agent_name, agent_session_id=session_id, remote_path=remote_path, @@ -372,3 +381,60 @@ def download_session_file_to_disk( with open(file_path, "wb") as f: for chunk in content_iterator: f.write(chunk) + + @distributed_trace + def download_code_to_path( + self, + agent_name: str, + *, + file_path: Union[str, "os.PathLike[str]"], + overwrite: bool = False, + agent_version: Optional[str] = None, + **kwargs: Any, + ) -> str: + """Download agent code directly to disk. + + Downloads the code zip for a code-based hosted agent and writes it to a local file. + + If ``agent_version`` is supplied, downloads that version's code zip; otherwise + downloads the latest version's code zip. + + :param agent_name: The name of the agent. Required. + :type agent_name: str + :keyword file_path: The full path to the local file where the code zip should be written. Required. + :paramtype file_path: str or os.PathLike[str] + :keyword overwrite: If True, overwrite the local file if it already exists. If False (default), + raise FileExistsError when the file already exists. + :paramtype overwrite: bool + :keyword agent_version: The version of the agent whose code zip should be downloaded. + If omitted, the latest version's code zip is downloaded. Default value is None. + :paramtype agent_version: str + :return: The SHA-256 hex digest of the downloaded file. + :rtype: str + :raises ~azure.core.exceptions.HttpResponseError: + :raises FileExistsError: If *file_path* already exists and *overwrite* is False. + :raises ValueError: If *file_path* points to a directory. + :raises OSError: If the file cannot be written. + """ + p = Path(file_path) + if p.exists(): + if p.is_dir(): + raise ValueError(f"Provide a valid file path, not a folder path `{file_path}`.") + if not overwrite: + raise FileExistsError(f"The file `{file_path}` already exists. Set overwrite=True to replace it.") + + # Download the code content using the existing method + content_iterator = self.download_code_as_bytes( + agent_name=agent_name, + agent_version=agent_version, + **kwargs, + ) + + # Write the content to disk and calculate SHA-256 + sha = hashlib.sha256() + with open(file_path, "wb") as f: + for chunk in content_iterator: + f.write(chunk) + sha.update(chunk) + + return sha.hexdigest() diff --git a/sdk/ai/azure-ai-projects/docs/public-methods.md b/sdk/ai/azure-ai-projects/docs/public-methods.md index be375d4609f5..edcca485b7e6 100644 --- a/sdk/ai/azure-ai-projects/docs/public-methods.md +++ b/sdk/ai/azure-ai-projects/docs/public-methods.md @@ -4,16 +4,16 @@ This document lists all public methods available on `AIProjectClient` and its su ## Summary -There are a total of 142 unique public methods: +There are a total of 143 unique public methods: - 5 stable methods on the client -- 56 stable methods on top-level sub-clients +- 57 stable methods on top-level sub-clients - 81 beta methods on nested beta sub-clients ### Top-level sub-clients (stable operations) | Subclient | Class Name | Methods Count | |-----------|------------|----------------| -| `agents` | AgentsOperations | 24 | +| `agents` | AgentsOperations | 25 | | `connections` | ConnectionsOperations | 3 | | `datasets` | DatasetsOperations | 9 | | `deployments` | DeploymentsOperations | 2 | @@ -65,9 +65,10 @@ Alphabetically sorted. An asterisk at the end of the method name means is a hand .agents.delete_session_file .agents.delete_version .agents.disable -.agents.download_code -.agents.download_session_file -.agents.download_session_file_to_disk* +.agents.download_code_as_bytes +.agents.download_code_to_path* +.agents.download_session_file_as_bytes +.agents.download_session_file_to_path* .agents.enable .agents.get .agents.get_session diff --git a/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_create_hosted_agent_from_code.py b/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_create_hosted_agent_from_code.py index 54e8a012c980..58f367f65ec3 100644 --- a/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_create_hosted_agent_from_code.py +++ b/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_create_hosted_agent_from_code.py @@ -39,15 +39,11 @@ REMOTE_BUILD; defaults to `false` (BUNDLED). """ -import hashlib import os import tempfile from pathlib import Path - from dotenv import load_dotenv - from azure.identity import DefaultAzureCredential - from azure.ai.projects import AIProjectClient from azure.ai.projects.models import ( CodeConfiguration, @@ -71,7 +67,7 @@ with ( DefaultAzureCredential() as credential, - AIProjectClient(endpoint=endpoint, credential=credential, allow_preview=True) as project_client, + AIProjectClient(endpoint=endpoint, credential=credential) as project_client, ): content = CreateAgentVersionFromCodeContent( metadata=CreateAgentVersionFromCodeMetadata( @@ -112,15 +108,14 @@ # Download the zip for the version we just created, streaming to a temp file. version_zip_path = Path(tempfile.gettempdir()) / f"{agent_name}-{created.version}.zip" - sha = hashlib.sha256() - with open(version_zip_path, "wb") as f: - for chunk in project_client.agents.download_code( - agent_name=agent_name, - agent_version=created.version, - ): - f.write(chunk) - sha.update(chunk) - downloaded_version_sha256 = sha.hexdigest() + + downloaded_version_sha256 = project_client.agents.download_code_to_path( + agent_name=agent_name, + agent_version=created.version, + file_path=version_zip_path, + overwrite=True, + ) + print( f"Downloaded version code zip to {version_zip_path}: {version_zip_path.stat().st_size} bytes, " f"sha256={downloaded_version_sha256} (matches uploaded: {downloaded_version_sha256 == code_zip_sha256})" diff --git a/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_create_hosted_agent_from_code_async.py b/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_create_hosted_agent_from_code_async.py index 85a597130058..07018c9fb4e5 100644 --- a/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_create_hosted_agent_from_code_async.py +++ b/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_create_hosted_agent_from_code_async.py @@ -41,15 +41,11 @@ """ import asyncio -import hashlib import os import tempfile from pathlib import Path - from dotenv import load_dotenv - from azure.identity.aio import DefaultAzureCredential - from azure.ai.projects.aio import AIProjectClient from azure.ai.projects.models import ( CodeConfiguration, @@ -75,7 +71,7 @@ async def main() -> None: async with ( DefaultAzureCredential() as credential, - AIProjectClient(endpoint=endpoint, credential=credential, allow_preview=True) as project_client, + AIProjectClient(endpoint=endpoint, credential=credential) as project_client, ): content = CreateAgentVersionFromCodeContent( metadata=CreateAgentVersionFromCodeMetadata( @@ -118,16 +114,14 @@ async def main() -> None: # Download the zip for the version we just created, streaming to a temp file. version_zip_path = Path(tempfile.gettempdir()) / f"{agent_name}-{created.version}.zip" - sha = hashlib.sha256() - version_stream = await project_client.agents.download_code( + + downloaded_version_sha256 = await project_client.agents.download_code_to_path( agent_name=agent_name, agent_version=created.version, + file_path=version_zip_path, + overwrite=True, ) - with open(version_zip_path, "wb") as f: - async for chunk in version_stream: - f.write(chunk) - sha.update(chunk) - downloaded_version_sha256 = sha.hexdigest() + print( f"Downloaded version code zip to {version_zip_path}: {version_zip_path.stat().st_size} bytes, " f"sha256={downloaded_version_sha256} (matches uploaded: {downloaded_version_sha256 == code_zip_sha256})" diff --git a/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_sessions_files_upload_download.py b/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_sessions_files_upload_download.py index 4087a45cd9fa..d4ed7e998b48 100644 --- a/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_sessions_files_upload_download.py +++ b/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_sessions_files_upload_download.py @@ -92,7 +92,7 @@ print(f"Downloading and printing content from '{remote_file_path1}'") content_bytes = b"".join( - project_client.agents.download_session_file( + project_client.agents.download_session_file_as_bytes( agent_name=agent_name, agent_session_id=session.agent_session_id, remote_path=remote_file_path1, diff --git a/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_sessions_files_upload_download_async.py b/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_sessions_files_upload_download_async.py index c87f17d4dac0..9bc61c5273c5 100644 --- a/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_sessions_files_upload_download_async.py +++ b/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_sessions_files_upload_download_async.py @@ -94,7 +94,7 @@ async def main(): print(f"Downloading and printing content from '{remote_file_path1}'") content_bytes = b"" - async for chunk in await project_client.agents.download_session_file( + async for chunk in await project_client.agents.download_session_file_as_bytes( agent_name=agent_name, agent_session_id=session.agent_session_id, remote_path=remote_file_path1, diff --git a/sdk/ai/azure-ai-projects/tests/sessions/test_agent_session_files_crud.py b/sdk/ai/azure-ai-projects/tests/sessions/test_agent_session_files_crud.py index 1b76a3ddf53b..55f9573a1854 100644 --- a/sdk/ai/azure-ai-projects/tests/sessions/test_agent_session_files_crud.py +++ b/sdk/ai/azure-ai-projects/tests/sessions/test_agent_session_files_crud.py @@ -57,7 +57,7 @@ def test_agent_session_files_crud(self, **kwargs): POST /agents/{agent_name}/sessions project_client.agents.create_session() POST /agents/{agent_name}/sessions/{session_id}/files:upload project_client.agents.upload_session_file() GET /agents/{agent_name}/sessions/{session_id}/files project_client.agents.list_session_files() - GET /agents/{agent_name}/sessions/{session_id}/files:download project_client.agents.download_session_file() + GET /agents/{agent_name}/sessions/{session_id}/files:download project_client.agents.download_session_file_as_bytes() DELETE /agents/{agent_name}/sessions/{session_id}/files project_client.agents.delete_session_file() DELETE /agents/{agent_name}/sessions/{session_id} project_client.agents.delete_session() """ @@ -161,7 +161,7 @@ def test_agent_session_files_crud(self, **kwargs): # Download and verify content of first file print(f"Downloading and verifying content from '{remote_file_path1}'") content_bytes = b"".join( - project_client.agents.download_session_file( + project_client.agents.download_session_file_as_bytes( agent_name=agent_name, agent_session_id=session.agent_session_id, remote_path=remote_file_path1, @@ -180,12 +180,12 @@ def test_agent_session_files_crud(self, **kwargs): ), f"Expected content '{expected_content}' not found in downloaded file" print("Content verification passed!") - # Download second file to disk using download_session_file_to_disk with str file_path + # Download second file to disk using download_session_file_to_path with str file_path temp_dir = tempfile.gettempdir() download_path = os.path.join(temp_dir, "downloaded_data_file2.txt") print(f"Downloading session file to disk: {remote_file_path2} -> {download_path}") - project_client.agents.download_session_file_to_disk( + project_client.agents.download_session_file_to_path( agent_name=agent_name, session_id=session.agent_session_id, file_path=download_path, # str type @@ -203,7 +203,7 @@ def test_agent_session_files_crud(self, **kwargs): assert ( expected_content2 in downloaded_content ), f"Expected content '{expected_content2}' not found in downloaded file" - print("download_session_file_to_disk content verification passed!") + print("download_session_file_to_path content verification passed!") # Clean up local temp file if os.path.exists(download_path): @@ -212,11 +212,11 @@ def test_agent_session_files_crud(self, **kwargs): # -------------------------------------------------------------------------------------------------- - # Download third file to disk using download_session_file_to_disk with PathLike file_path + # Download third file to disk using download_session_file_to_path with PathLike file_path download_path3 = Path(tempfile.gettempdir()) / "downloaded_data_file3.txt" print(f"Downloading session file to disk using PathLike: {remote_file_path3} -> {download_path3}") - project_client.agents.download_session_file_to_disk( + project_client.agents.download_session_file_to_path( agent_name=agent_name, session_id=session.agent_session_id, file_path=download_path3, # PathLike[str] type @@ -234,7 +234,7 @@ def test_agent_session_files_crud(self, **kwargs): assert ( expected_content3 in downloaded_content3 ), f"Expected content '{expected_content3}' not found in downloaded file" - print("download_session_file_to_disk with PathLike content verification passed!") + print("download_session_file_to_path with PathLike content verification passed!") # Clean up local temp file if download_path3.exists(): @@ -280,32 +280,22 @@ def test_agent_session_files_crud(self, **kwargs): # To run this test: # pytest tests\sessions\test_agent_session_files_crud.py::TestAgentSessionFilesCrud::test_agent_session_files_invalid_input -s + # These are unit-tests that do not make network calls. @servicePreparer() - @recorded_by_proxy() def test_agent_session_files_invalid_input(self, **kwargs): """ - Test that upload_session_file and download_session_file_to_disk raise appropriate + Test that upload_session_file and download_session_file_to_path raise appropriate errors when given invalid input (non-existing files, folder paths). These are client-side validations that occur before any API call is made. """ print("\n") - agent_name = kwargs["foundry_hosted_agent_name"] - project_client = self.create_client(**kwargs) - - # Get the latest active agent version - agent = self._get_latest_active_agent_version(project_client, agent_name) - assert agent is not None, "Failed to get agent version" - print(f"Using agent: {agent_name}, version: {agent.version}") + foundry_project_endpoint = "https://fake-endpoint" + agent_name = "fake-agent-name" + session_id = "fake-session-id" - # Create a session - session = project_client.agents.create_session( - agent_name=agent_name, - version_indicator=VersionRefIndicator(agent_version=agent.version), - ) - assert session is not None, "Session creation returned None" - print(f"Session created (id: {session.agent_session_id}, status: {session.status})") + project_client = self.create_client(agent_name=agent_name, foundry_project_endpoint=foundry_project_endpoint) try: # -------------------------------------------------------------------------------------------------- @@ -318,7 +308,7 @@ def test_agent_session_files_invalid_input(self, **kwargs): try: project_client.agents.upload_session_file( agent_name=agent_name, - session_id=session.agent_session_id, + session_id=session_id, file_path=non_existing_file_str, # str type pointing to non-existing file remote_path="/remote/non_existing.txt", ) @@ -333,7 +323,7 @@ def test_agent_session_files_invalid_input(self, **kwargs): try: project_client.agents.upload_session_file( agent_name=agent_name, - session_id=session.agent_session_id, + session_id=session_id, file_path=non_existing_file_pathlike, # PathLike[str] type pointing to non-existing file remote_path="/remote/non_existing.txt", ) @@ -348,7 +338,7 @@ def test_agent_session_files_invalid_input(self, **kwargs): try: project_client.agents.upload_session_file( agent_name=agent_name, - session_id=session.agent_session_id, + session_id=session_id, file_path=upload_folder_path_str, # str type pointing to a folder remote_path="/remote/folder_upload.txt", ) @@ -363,7 +353,7 @@ def test_agent_session_files_invalid_input(self, **kwargs): try: project_client.agents.upload_session_file( agent_name=agent_name, - session_id=session.agent_session_id, + session_id=session_id, file_path=upload_folder_path_pathlike, # PathLike[str] type pointing to a folder remote_path="/remote/folder_upload.txt", ) @@ -375,16 +365,16 @@ def test_agent_session_files_invalid_input(self, **kwargs): print("upload_session_file error handling tests passed!") # -------------------------------------------------------------------------------------------------- - # Test download_session_file_to_disk with invalid inputs + # Test download_session_file_to_path with invalid inputs # -------------------------------------------------------------------------------------------------- - # Test that download_session_file_to_disk raises ValueError when file_path is a folder (str type) + # Test that download_session_file_to_path raises ValueError when file_path is a folder (str type) folder_path_str = tempfile.gettempdir() # This is a folder, not a file - print(f"Testing download_session_file_to_disk with folder path (str): {folder_path_str}") + print(f"Testing download_session_file_to_path with folder path (str): {folder_path_str}") try: - project_client.agents.download_session_file_to_disk( + project_client.agents.download_session_file_to_path( agent_name=agent_name, - session_id=session.agent_session_id, + session_id=session_id, file_path=folder_path_str, # str type pointing to a folder remote_path="/remote/some_file.txt", ) @@ -393,13 +383,13 @@ def test_agent_session_files_invalid_input(self, **kwargs): print(f"Got expected ValueError for folder path (str): {e}") assert "folder" in str(e).lower(), f"Error message should mention 'folder': {e}" - # Test that download_session_file_to_disk raises ValueError when file_path is a folder (PathLike type) + # Test that download_session_file_to_path raises ValueError when file_path is a folder (PathLike type) folder_path_pathlike = Path(tempfile.gettempdir()) # This is a folder, not a file - print(f"Testing download_session_file_to_disk with folder path (PathLike): {folder_path_pathlike}") + print(f"Testing download_session_file_to_path with folder path (PathLike): {folder_path_pathlike}") try: - project_client.agents.download_session_file_to_disk( + project_client.agents.download_session_file_to_path( agent_name=agent_name, - session_id=session.agent_session_id, + session_id=session_id, file_path=folder_path_pathlike, # PathLike[str] type pointing to a folder remote_path="/remote/some_file.txt", ) @@ -408,13 +398,63 @@ def test_agent_session_files_invalid_input(self, **kwargs): print(f"Got expected ValueError for folder path (PathLike): {e}") assert "folder" in str(e).lower(), f"Error message should mention 'folder': {e}" - print("download_session_file_to_disk folder path validation tests passed!") + print("download_session_file_to_path folder path validation tests passed!") + + # -------------------------------------------------------------------------------------------------- + # Test download_session_file_to_path with existing file (overwrite behavior) + # -------------------------------------------------------------------------------------------------- + + # Create a temporary file that already exists + existing_file_path = os.path.join(tempfile.gettempdir(), "existing_file_for_overwrite_test.txt") + with open(existing_file_path, "w", encoding="utf-8") as f: + f.write("This file already exists") + + try: + # Test that download_session_file_to_path raises FileExistsError when file exists (default overwrite=False) + print( + f"Testing download_session_file_to_path with existing file (default overwrite): {existing_file_path}" + ) + try: + project_client.agents.download_session_file_to_path( + agent_name=agent_name, + session_id=session_id, + file_path=existing_file_path, + remote_path="/remote/some_file.txt", + ) + assert False, "Expected FileExistsError when file already exists (default overwrite=False)" + except FileExistsError as e: + print(f"Got expected FileExistsError (default overwrite): {e}") + assert "already exists" in str(e).lower(), f"Error message should mention 'already exists': {e}" + assert "overwrite=True" in str(e), f"Error message should mention 'overwrite=True': {e}" + + # Test that download_session_file_to_path raises FileExistsError when file exists with explicit overwrite=False + print( + f"Testing download_session_file_to_path with existing file (explicit overwrite=False): {existing_file_path}" + ) + try: + project_client.agents.download_session_file_to_path( + agent_name=agent_name, + session_id=session_id, + file_path=existing_file_path, + overwrite=False, + remote_path="/remote/some_file.txt", + ) + assert False, "Expected FileExistsError when file already exists (explicit overwrite=False)" + except FileExistsError as e: + print(f"Got expected FileExistsError (explicit overwrite=False): {e}") + assert "already exists" in str(e).lower(), f"Error message should mention 'already exists': {e}" + assert "overwrite=True" in str(e), f"Error message should mention 'overwrite=True': {e}" + + print("download_session_file_to_path overwrite validation tests passed!") + + finally: + # Clean up the temporary file + if os.path.exists(existing_file_path): + os.remove(existing_file_path) + print(f"Cleaned up temp file: {existing_file_path}") + print("All invalid input tests passed!") finally: - # Clean up: delete the session - project_client.agents.delete_session( - agent_name=agent_name, - session_id=session.agent_session_id, - ) - print(f"Session deleted (id: {session.agent_session_id})") + # Add any cleanup here + ... diff --git a/sdk/ai/azure-ai-projects/tests/sessions/test_agent_session_files_crud_async.py b/sdk/ai/azure-ai-projects/tests/sessions/test_agent_session_files_crud_async.py index eaa52e5ae3a1..46c3d8a3ae6d 100644 --- a/sdk/ai/azure-ai-projects/tests/sessions/test_agent_session_files_crud_async.py +++ b/sdk/ai/azure-ai-projects/tests/sessions/test_agent_session_files_crud_async.py @@ -57,7 +57,7 @@ async def test_agent_session_files_crud_async(self, **kwargs): POST /agents/{agent_name}/sessions project_client.agents.create_session() POST /agents/{agent_name}/sessions/{session_id}/files:upload project_client.agents.upload_session_file() GET /agents/{agent_name}/sessions/{session_id}/files project_client.agents.list_session_files() - GET /agents/{agent_name}/sessions/{session_id}/files:download project_client.agents.download_session_file() + GET /agents/{agent_name}/sessions/{session_id}/files:download project_client.agents.download_session_file_as_bytes() DELETE /agents/{agent_name}/sessions/{session_id}/files project_client.agents.delete_session_file() DELETE /agents/{agent_name}/sessions/{session_id} project_client.agents.delete_session() """ @@ -160,7 +160,7 @@ async def test_agent_session_files_crud_async(self, **kwargs): # Download and verify content of first file print(f"Downloading and verifying content from '{remote_file_path1}'") content_chunks = [] - download_iterator = await project_client.agents.download_session_file( + download_iterator = await project_client.agents.download_session_file_as_bytes( agent_name=agent_name, agent_session_id=session.agent_session_id, remote_path=remote_file_path1, @@ -182,12 +182,12 @@ async def test_agent_session_files_crud_async(self, **kwargs): ), f"Expected content '{expected_content}' not found in downloaded file" print("Content verification passed!") - # Download second file to disk using download_session_file_to_disk with str file_path + # Download second file to disk using download_session_file_to_path with str file_path temp_dir = tempfile.gettempdir() download_path = os.path.join(temp_dir, "downloaded_data_file2.txt") print(f"Downloading session file to disk: {remote_file_path2} -> {download_path}") - await project_client.agents.download_session_file_to_disk( + await project_client.agents.download_session_file_to_path( agent_name=agent_name, session_id=session.agent_session_id, file_path=download_path, # str type @@ -205,7 +205,7 @@ async def test_agent_session_files_crud_async(self, **kwargs): assert ( expected_content2 in downloaded_content ), f"Expected content '{expected_content2}' not found in downloaded file" - print("download_session_file_to_disk content verification passed!") + print("download_session_file_to_path content verification passed!") # Clean up local temp file if os.path.exists(download_path): @@ -214,11 +214,11 @@ async def test_agent_session_files_crud_async(self, **kwargs): # -------------------------------------------------------------------------------------------------- - # Download third file to disk using download_session_file_to_disk with PathLike file_path + # Download third file to disk using download_session_file_to_path with PathLike file_path download_path3 = Path(tempfile.gettempdir()) / "downloaded_data_file3.txt" print(f"Downloading session file to disk using PathLike: {remote_file_path3} -> {download_path3}") - await project_client.agents.download_session_file_to_disk( + await project_client.agents.download_session_file_to_path( agent_name=agent_name, session_id=session.agent_session_id, file_path=download_path3, # PathLike[str] type @@ -236,7 +236,7 @@ async def test_agent_session_files_crud_async(self, **kwargs): assert ( expected_content3 in downloaded_content3 ), f"Expected content '{expected_content3}' not found in downloaded file" - print("download_session_file_to_disk with PathLike content verification passed!") + print("download_session_file_to_path with PathLike content verification passed!") # Clean up local temp file if download_path3.exists(): @@ -282,33 +282,26 @@ async def test_agent_session_files_crud_async(self, **kwargs): # To run this test: # pytest tests\sessions\test_agent_session_files_crud_async.py::TestAgentSessionFilesCrudAsync::test_agent_session_files_invalid_input_async -s + # These are unit-tests that do not make network calls. @servicePreparer() - @recorded_by_proxy_async() async def test_agent_session_files_invalid_input_async(self, **kwargs): """ - Test that upload_session_file and download_session_file_to_disk raise appropriate + Test that upload_session_file and download_session_file_to_path raise appropriate errors when given invalid input (non-existing files, folder paths). These are client-side validations that occur before any API call is made. """ print("\n") - agent_name = kwargs["foundry_hosted_agent_name"] - project_client = self.create_async_client(**kwargs) + foundry_project_endpoint = "https://fake-endpoint" + agent_name = "fake-agent-name" + session_id = "fake-session-id" - async with project_client: - # Get the latest active agent version - agent = await self._get_latest_active_agent_version_async(project_client, agent_name) - assert agent is not None, "Failed to get agent version" - print(f"Using agent: {agent_name}, version: {agent.version}") + project_client = self.create_async_client( + agent_name=agent_name, foundry_project_endpoint=foundry_project_endpoint + ) - # Create a session - session = await project_client.agents.create_session( - agent_name=agent_name, - version_indicator=VersionRefIndicator(agent_version=agent.version), - ) - assert session is not None, "Session creation returned None" - print(f"Session created (id: {session.agent_session_id}, status: {session.status})") + async with project_client: try: # -------------------------------------------------------------------------------------------------- @@ -321,7 +314,7 @@ async def test_agent_session_files_invalid_input_async(self, **kwargs): try: await project_client.agents.upload_session_file( agent_name=agent_name, - session_id=session.agent_session_id, + session_id=session_id, file_path=non_existing_file_str, # str type pointing to non-existing file remote_path="/remote/non_existing.txt", ) @@ -336,7 +329,7 @@ async def test_agent_session_files_invalid_input_async(self, **kwargs): try: await project_client.agents.upload_session_file( agent_name=agent_name, - session_id=session.agent_session_id, + session_id=session_id, file_path=non_existing_file_pathlike, # PathLike[str] type pointing to non-existing file remote_path="/remote/non_existing.txt", ) @@ -351,7 +344,7 @@ async def test_agent_session_files_invalid_input_async(self, **kwargs): try: await project_client.agents.upload_session_file( agent_name=agent_name, - session_id=session.agent_session_id, + session_id=session_id, file_path=upload_folder_path_str, # str type pointing to a folder remote_path="/remote/folder_upload.txt", ) @@ -366,7 +359,7 @@ async def test_agent_session_files_invalid_input_async(self, **kwargs): try: await project_client.agents.upload_session_file( agent_name=agent_name, - session_id=session.agent_session_id, + session_id=session_id, file_path=upload_folder_path_pathlike, # PathLike[str] type pointing to a folder remote_path="/remote/folder_upload.txt", ) @@ -378,16 +371,16 @@ async def test_agent_session_files_invalid_input_async(self, **kwargs): print("upload_session_file error handling tests passed!") # -------------------------------------------------------------------------------------------------- - # Test download_session_file_to_disk with invalid inputs + # Test download_session_file_to_path with invalid inputs # -------------------------------------------------------------------------------------------------- - # Test that download_session_file_to_disk raises ValueError when file_path is a folder (str type) + # Test that download_session_file_to_path raises ValueError when file_path is a folder (str type) folder_path_str = tempfile.gettempdir() # This is a folder, not a file - print(f"Testing download_session_file_to_disk with folder path (str): {folder_path_str}") + print(f"Testing download_session_file_to_path with folder path (str): {folder_path_str}") try: - await project_client.agents.download_session_file_to_disk( + await project_client.agents.download_session_file_to_path( agent_name=agent_name, - session_id=session.agent_session_id, + session_id=session_id, file_path=folder_path_str, # str type pointing to a folder remote_path="/remote/some_file.txt", ) @@ -396,13 +389,13 @@ async def test_agent_session_files_invalid_input_async(self, **kwargs): print(f"Got expected ValueError for folder path (str): {e}") assert "folder" in str(e).lower(), f"Error message should mention 'folder': {e}" - # Test that download_session_file_to_disk raises ValueError when file_path is a folder (PathLike type) + # Test that download_session_file_to_path raises ValueError when file_path is a folder (PathLike type) folder_path_pathlike = Path(tempfile.gettempdir()) # This is a folder, not a file - print(f"Testing download_session_file_to_disk with folder path (PathLike): {folder_path_pathlike}") + print(f"Testing download_session_file_to_path with folder path (PathLike): {folder_path_pathlike}") try: - await project_client.agents.download_session_file_to_disk( + await project_client.agents.download_session_file_to_path( agent_name=agent_name, - session_id=session.agent_session_id, + session_id=session_id, file_path=folder_path_pathlike, # PathLike[str] type pointing to a folder remote_path="/remote/some_file.txt", ) @@ -411,13 +404,63 @@ async def test_agent_session_files_invalid_input_async(self, **kwargs): print(f"Got expected ValueError for folder path (PathLike): {e}") assert "folder" in str(e).lower(), f"Error message should mention 'folder': {e}" - print("download_session_file_to_disk folder path validation tests passed!") + print("download_session_file_to_path folder path validation tests passed!") + + # -------------------------------------------------------------------------------------------------- + # Test download_session_file_to_path with existing file (overwrite behavior) + # -------------------------------------------------------------------------------------------------- + + # Create a temporary file that already exists + existing_file_path = os.path.join(tempfile.gettempdir(), "existing_file_for_overwrite_test.txt") + with open(existing_file_path, "w", encoding="utf-8") as f: + f.write("This file already exists") + + try: + # Test that download_session_file_to_path raises FileExistsError when file exists (default overwrite=False) + print( + f"Testing download_session_file_to_path with existing file (default overwrite): {existing_file_path}" + ) + try: + await project_client.agents.download_session_file_to_path( + agent_name=agent_name, + session_id=session_id, + file_path=existing_file_path, + remote_path="/remote/some_file.txt", + ) + assert False, "Expected FileExistsError when file already exists (default overwrite=False)" + except FileExistsError as e: + print(f"Got expected FileExistsError (default overwrite): {e}") + assert "already exists" in str(e).lower(), f"Error message should mention 'already exists': {e}" + assert "overwrite=True" in str(e), f"Error message should mention 'overwrite=True': {e}" + + # Test that download_session_file_to_path raises FileExistsError when file exists with explicit overwrite=False + print( + f"Testing download_session_file_to_path with existing file (explicit overwrite=False): {existing_file_path}" + ) + try: + await project_client.agents.download_session_file_to_path( + agent_name=agent_name, + session_id=session_id, + file_path=existing_file_path, + overwrite=False, + remote_path="/remote/some_file.txt", + ) + assert False, "Expected FileExistsError when file already exists (explicit overwrite=False)" + except FileExistsError as e: + print(f"Got expected FileExistsError (explicit overwrite=False): {e}") + assert "already exists" in str(e).lower(), f"Error message should mention 'already exists': {e}" + assert "overwrite=True" in str(e), f"Error message should mention 'overwrite=True': {e}" + + print("download_session_file_to_path overwrite validation tests passed!") + + finally: + # Clean up the temporary file + if os.path.exists(existing_file_path): + os.remove(existing_file_path) + print(f"Cleaned up temp file: {existing_file_path}") + print("All invalid input tests passed!") finally: - # Clean up: delete the session - await project_client.agents.delete_session( - agent_name=agent_name, - session_id=session.agent_session_id, - ) - print(f"Session deleted (id: {session.agent_session_id})") + # Add any cleanup here + ... diff --git a/sdk/ai/azure-ai-projects/tsp-location.yaml b/sdk/ai/azure-ai-projects/tsp-location.yaml index dc53736e08cb..4776e3a51121 100644 --- a/sdk/ai/azure-ai-projects/tsp-location.yaml +++ b/sdk/ai/azure-ai-projects/tsp-location.yaml @@ -1,5 +1,5 @@ directory: specification/ai-foundry/data-plane/Foundry/src/sdk-python-js-azure-ai-projects -commit: 32a5840d5292b854ae7f7ffc4f699c9ca5d03a05 +commit: 69d2f95dc1af612d44f2975dfb198a4506bc2a95 repo: Azure/azure-rest-api-specs additionalDirectories: - specification/ai-foundry/data-plane/Foundry/src/agents