diff --git a/sdk/ai/azure-ai-projects/CHANGELOG.md b/sdk/ai/azure-ai-projects/CHANGELOG.md index 1101a49c760b..10fa8bd1c474 100644 --- a/sdk/ai/azure-ai-projects/CHANGELOG.md +++ b/sdk/ai/azure-ai-projects/CHANGELOG.md @@ -11,8 +11,9 @@ ### Breaking Changes -* Renamed model `AgentEndpoint` to `AgentEndpointConfig`. -* Agent Endpoint beta operations: Removed required parameters `user_isolation_key` and `chat_isolation_key` from the `HeaderIsolationKeySource` class constructor. +* Agent Endpoint beta operations: + * Renamed model `AgentEndpoint` to `AgentEndpointConfig`. + * Removed previously required parameters `user_isolation_key` and `chat_isolation_key` from the `HeaderIsolationKeySource` class constructor. ### Bugs Fixed * Fixed telemetry instrumentor to correctly call is_recording() as a method on spans, ensuring non-recording spans are properly skipped (e.g., when sampling is configured) ([GitHub issue 46544](https://github.com/Azure/azure-sdk-for-python/issues/46544)). diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py index 4c9c649d30f1..69a15dc914bb 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py @@ -8,6 +8,7 @@ Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ +import warnings from typing import Final, FrozenSet, List, Dict, Mapping, Optional, Any, Tuple from azure.core.polling import LROPoller, AsyncLROPoller, PollingMethod, AsyncPollingMethod from azure.core.polling.base_polling import ( @@ -390,6 +391,38 @@ def from_continuation_token( ] # Add all objects you want publicly available to users at this package level +# Deprecated names handled via _models_getattr injected by patch_sdk() +_DEPRECATED_NAMES: Dict[str, str] = { + # Deprecated Name: Replacement Name + "AgentEndpoint": "AgentEndpointConfig", # Deprecated in v2.2.0 +} + +def _models_getattr(name: str) -> Any: + """Module-level __getattr__ for deprecation warnings on azure.ai.projects.models. + + This function is injected into the models module by patch_sdk() to handle + deprecated name lookups (PEP 562). + + :param name: The attribute name being accessed. + :type name: str + :return: The replacement attribute value. + :rtype: Any + :raises AttributeError: If the attribute is not found. + """ + import sys + + if name in _DEPRECATED_NAMES: + replacement = _DEPRECATED_NAMES[name] + warnings.warn( + f"'{name}' is deprecated and will be removed in the next release. " + f"Use '{replacement}' instead.", + DeprecationWarning, + stacklevel=2, + ) + return getattr(sys.modules["azure.ai.projects.models"], replacement) + raise AttributeError(f"module 'azure.ai.projects.models' has no attribute {name!r}") + + def patch_sdk(): """Do not remove from this file. @@ -397,3 +430,9 @@ def patch_sdk(): you can't accomplish using the techniques described in https://aka.ms/azsdk/python/dpcodegen/python/customize """ + import sys + + # Inject __getattr__ into the models module for PEP 562 deprecation handling (https://peps.python.org/pep-0562/) + models_module = sys.modules.get("azure.ai.projects.models") + if models_module is not None: + models_module.__getattr__ = _models_getattr # type: ignore[attr-defined] diff --git a/sdk/ai/azure-ai-projects/tests/deprecation/test_agent_endpoint_deprecation.py b/sdk/ai/azure-ai-projects/tests/deprecation/test_agent_endpoint_deprecation.py new file mode 100644 index 000000000000..97633623523b --- /dev/null +++ b/sdk/ai/azure-ai-projects/tests/deprecation/test_agent_endpoint_deprecation.py @@ -0,0 +1,71 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +"""Tests for AgentEndpoint deprecation warning.""" + +import warnings + +class TestAgentEndpointDeprecation: + """Test that AgentEndpoint is deprecated but still functional (PEP 562 __getattr__).""" + + def test_agent_endpoint_emits_deprecation_warning_on_access(self): + """Test that accessing AgentEndpoint emits a DeprecationWarning (PEP 562).""" + import azure.ai.projects.models as models + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + # Warning is emitted on attribute access, not instantiation + _ = models.AgentEndpoint # type: ignore[attr-defined] + + assert len(w) == 1 + assert issubclass(w[0].category, DeprecationWarning) + assert "AgentEndpoint" in str(w[0].message) + assert "AgentEndpointConfig" in str(w[0].message) + + def test_agent_endpoint_config_no_warning(self): + """Test that using AgentEndpointConfig does not emit a warning.""" + import azure.ai.projects.models as models + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + _ = models.AgentEndpointConfig + config = models.AgentEndpointConfig() + + # Filter only DeprecationWarnings related to AgentEndpoint + deprecation_warnings = [ + warning for warning in w + if issubclass(warning.category, DeprecationWarning) + and "AgentEndpoint" in str(warning.message) + ] + assert len(deprecation_warnings) == 0 + + def test_agent_endpoint_is_same_class_as_config(self): + """Test that AgentEndpoint returns the same class as AgentEndpointConfig (PEP 562).""" + import azure.ai.projects.models as models + + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + # With PEP 562 __getattr__, AgentEndpoint IS AgentEndpointConfig + assert models.AgentEndpoint is models.AgentEndpointConfig # type: ignore[attr-defined] + + def test_agent_endpoint_instance_is_config_instance(self): + """Test that AgentEndpoint instance is also an AgentEndpointConfig instance.""" + import azure.ai.projects.models as models + + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + endpoint = models.AgentEndpoint() # type: ignore[attr-defined] + + assert isinstance(endpoint, models.AgentEndpointConfig) + + def test_agent_endpoint_functionality_preserved(self): + """Test that AgentEndpoint still works with all its parameters.""" + import azure.ai.projects.models as models + + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + endpoint = models.AgentEndpoint( # type: ignore[attr-defined] + protocols=[models.AgentEndpointProtocol.A2A], + ) + + assert endpoint.protocols == [models.AgentEndpointProtocol.A2A]