From 5d3239a3a9853e512322593c77be4d21c8255ba7 Mon Sep 17 00:00:00 2001 From: chowhan arjun Date: Fri, 20 Feb 2026 16:03:29 -0500 Subject: [PATCH 1/5] Fix audience string mangling in TextTranslationClient --- .../azure/ai/translation/text/_patch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/translation/azure-ai-translation-text/azure/ai/translation/text/_patch.py b/sdk/translation/azure-ai-translation-text/azure/ai/translation/text/_patch.py index 9c58006b2016..2bdb2c4d7313 100644 --- a/sdk/translation/azure-ai-translation-text/azure/ai/translation/text/_patch.py +++ b/sdk/translation/azure-ai-translation-text/azure/ai/translation/text/_patch.py @@ -98,7 +98,7 @@ def set_authentication_policy(credential, kwargs): elif hasattr(credential, "get_token"): if not kwargs.get("authentication_policy"): if kwargs.get("region") and kwargs.get("resource_id"): - scope = kwargs.pop("audience", DEFAULT_ENTRA_ID_SCOPE).rstrip("/").rstrip(DEFAULT_SCOPE) + DEFAULT_SCOPE + scope = kwargs.pop("audience", DEFAULT_ENTRA_ID_SCOPE).removesuffix("/.default").removesuffix("/") + DEFAULT_SCOPE kwargs["authentication_policy"] = TranslatorEntraIdAuthenticationPolicy( credential, kwargs["resource_id"], @@ -113,7 +113,7 @@ def set_authentication_policy(credential, kwargs): ) scope: str = kwargs.pop("audience", DEFAULT_ENTRA_ID_SCOPE) if not is_cognitive_services_scope(scope): - scope = scope.rstrip("/").rstrip(DEFAULT_SCOPE) + DEFAULT_SCOPE + scope = scope.removesuffix("/.default").removesuffix("/") + DEFAULT_SCOPE kwargs["authentication_policy"] = BearerTokenCredentialPolicy(credential, scope) From f81cbcf3fa43630a477167922104e84d234eae11 Mon Sep 17 00:00:00 2001 From: chowhan arjun Date: Fri, 20 Feb 2026 16:56:31 -0500 Subject: [PATCH 2/5] Fix: Replace removesuffix with Python 3.8 compatible string handling --- .../azure/ai/translation/text/_patch.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/sdk/translation/azure-ai-translation-text/azure/ai/translation/text/_patch.py b/sdk/translation/azure-ai-translation-text/azure/ai/translation/text/_patch.py index 2bdb2c4d7313..008fb9b8b490 100644 --- a/sdk/translation/azure-ai-translation-text/azure/ai/translation/text/_patch.py +++ b/sdk/translation/azure-ai-translation-text/azure/ai/translation/text/_patch.py @@ -98,7 +98,13 @@ def set_authentication_policy(credential, kwargs): elif hasattr(credential, "get_token"): if not kwargs.get("authentication_policy"): if kwargs.get("region") and kwargs.get("resource_id"): - scope = kwargs.pop("audience", DEFAULT_ENTRA_ID_SCOPE).removesuffix("/.default").removesuffix("/") + DEFAULT_SCOPE + audience = kwargs.pop("audience", DEFAULT_ENTRA_ID_SCOPE) + if audience.endswith(DEFAULT_SCOPE): + scope = audience + elif audience.endswith("/"): + scope = audience[:-1] + DEFAULT_SCOPE + else: + scope = audience + DEFAULT_SCOPE kwargs["authentication_policy"] = TranslatorEntraIdAuthenticationPolicy( credential, kwargs["resource_id"], @@ -113,7 +119,12 @@ def set_authentication_policy(credential, kwargs): ) scope: str = kwargs.pop("audience", DEFAULT_ENTRA_ID_SCOPE) if not is_cognitive_services_scope(scope): - scope = scope.removesuffix("/.default").removesuffix("/") + DEFAULT_SCOPE + if scope.endswith(DEFAULT_SCOPE): + pass # Already has /.default + elif scope.endswith("/"): + scope = scope[:-1] + DEFAULT_SCOPE + else: + scope = scope + DEFAULT_SCOPE kwargs["authentication_policy"] = BearerTokenCredentialPolicy(credential, scope) @@ -198,4 +209,4 @@ def __init__(self, **kwargs): super().__init__(endpoint=translation_endpoint, api_version=api_version, **kwargs) -__all__ = ["TextTranslationClient"] +__all__ = ["TextTranslationClient"] \ No newline at end of file From 3929b4ba6905c84ce9d98b94138af0bffaf707b0 Mon Sep 17 00:00:00 2001 From: chowhan arjun Date: Fri, 20 Feb 2026 20:41:58 -0500 Subject: [PATCH 3/5] Refactor scope handling in authentication logic --- .../azure/ai/translation/text/_patch.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/sdk/translation/azure-ai-translation-text/azure/ai/translation/text/_patch.py b/sdk/translation/azure-ai-translation-text/azure/ai/translation/text/_patch.py index 008fb9b8b490..8ba02ddce107 100644 --- a/sdk/translation/azure-ai-translation-text/azure/ai/translation/text/_patch.py +++ b/sdk/translation/azure-ai-translation-text/azure/ai/translation/text/_patch.py @@ -119,12 +119,11 @@ def set_authentication_policy(credential, kwargs): ) scope: str = kwargs.pop("audience", DEFAULT_ENTRA_ID_SCOPE) if not is_cognitive_services_scope(scope): - if scope.endswith(DEFAULT_SCOPE): - pass # Already has /.default - elif scope.endswith("/"): - scope = scope[:-1] + DEFAULT_SCOPE - else: - scope = scope + DEFAULT_SCOPE + if not scope.endswith(DEFAULT_SCOPE): + if scope.endswith("/"): + scope = scope[:-1] + DEFAULT_SCOPE + else: + scope = scope + DEFAULT_SCOPE kwargs["authentication_policy"] = BearerTokenCredentialPolicy(credential, scope) @@ -209,4 +208,4 @@ def __init__(self, **kwargs): super().__init__(endpoint=translation_endpoint, api_version=api_version, **kwargs) -__all__ = ["TextTranslationClient"] \ No newline at end of file +__all__ = ["TextTranslationClient"] From b1891600af1e6d6dc729126133d2f47d6d71015a Mon Sep 17 00:00:00 2001 From: chowhan arjun Date: Sat, 21 Feb 2026 02:27:53 +0000 Subject: [PATCH 4/5] Fix audience scope normalization and add regression tests --- .../azure/ai/translation/text/_patch.py | 27 +++++++++---------- .../tests/test_scope_normalization.py | 22 +++++++++++++++ 2 files changed, 35 insertions(+), 14 deletions(-) create mode 100644 sdk/translation/azure-ai-translation-text/tests/test_scope_normalization.py diff --git a/sdk/translation/azure-ai-translation-text/azure/ai/translation/text/_patch.py b/sdk/translation/azure-ai-translation-text/azure/ai/translation/text/_patch.py index 8ba02ddce107..d7b7d88055a9 100644 --- a/sdk/translation/azure-ai-translation-text/azure/ai/translation/text/_patch.py +++ b/sdk/translation/azure-ai-translation-text/azure/ai/translation/text/_patch.py @@ -4,7 +4,7 @@ # ------------------------------------ # pylint: disable=C4717, C4722, C4748 -from typing import Optional, Any, overload +from typing import Optional, Any, cast, overload from azure.core.pipeline import PipelineRequest from azure.core.pipeline.policies import SansIOHTTPPolicy, BearerTokenCredentialPolicy, AzureKeyCredentialPolicy from azure.core.credentials import TokenCredential, AzureKeyCredential @@ -86,6 +86,15 @@ def is_cognitive_services_scope(audience: str) -> bool: return False +def normalize_scope(audience: Optional[str]) -> str: + scope = audience or DEFAULT_ENTRA_ID_SCOPE + if scope.endswith(DEFAULT_SCOPE): + return scope + if scope.endswith("/"): + return scope[:-1] + DEFAULT_SCOPE + return scope + DEFAULT_SCOPE + + def set_authentication_policy(credential, kwargs): if isinstance(credential, AzureKeyCredential): if not kwargs.get("authentication_policy"): @@ -98,13 +107,7 @@ def set_authentication_policy(credential, kwargs): elif hasattr(credential, "get_token"): if not kwargs.get("authentication_policy"): if kwargs.get("region") and kwargs.get("resource_id"): - audience = kwargs.pop("audience", DEFAULT_ENTRA_ID_SCOPE) - if audience.endswith(DEFAULT_SCOPE): - scope = audience - elif audience.endswith("/"): - scope = audience[:-1] + DEFAULT_SCOPE - else: - scope = audience + DEFAULT_SCOPE + scope = normalize_scope(cast(Optional[str], kwargs.pop("audience", DEFAULT_ENTRA_ID_SCOPE))) kwargs["authentication_policy"] = TranslatorEntraIdAuthenticationPolicy( credential, kwargs["resource_id"], @@ -117,13 +120,9 @@ def set_authentication_policy(credential, kwargs): """Both 'resource_id' and 'region' must be provided with a TokenCredential for regional resource authentication.""" ) - scope: str = kwargs.pop("audience", DEFAULT_ENTRA_ID_SCOPE) + scope = cast(Optional[str], kwargs.pop("audience", DEFAULT_ENTRA_ID_SCOPE)) or DEFAULT_ENTRA_ID_SCOPE if not is_cognitive_services_scope(scope): - if not scope.endswith(DEFAULT_SCOPE): - if scope.endswith("/"): - scope = scope[:-1] + DEFAULT_SCOPE - else: - scope = scope + DEFAULT_SCOPE + scope = normalize_scope(scope) kwargs["authentication_policy"] = BearerTokenCredentialPolicy(credential, scope) diff --git a/sdk/translation/azure-ai-translation-text/tests/test_scope_normalization.py b/sdk/translation/azure-ai-translation-text/tests/test_scope_normalization.py new file mode 100644 index 000000000000..661af9368db1 --- /dev/null +++ b/sdk/translation/azure-ai-translation-text/tests/test_scope_normalization.py @@ -0,0 +1,22 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +import pytest + +from azure.ai.translation.text._patch import normalize_scope + + +@pytest.mark.parametrize( + "audience, expected", + [ + ("https://cognitiveservices.azure.com", "https://cognitiveservices.azure.com/.default"), + ("https://cognitiveservices.azure.com/", "https://cognitiveservices.azure.com/.default"), + ("https://cognitiveservices.azure.com/.default", "https://cognitiveservices.azure.com/.default"), + ("api://myapp-live", "api://myapp-live/.default"), + (None, "https://cognitiveservices.azure.com/.default"), + ], +) +def test_normalize_scope(audience, expected): + assert normalize_scope(audience) == expected From 5c379e250b6c4441b896e9ac96032f3482ff4ca1 Mon Sep 17 00:00:00 2001 From: chowhan arjun Date: Sat, 21 Feb 2026 05:09:26 +0000 Subject: [PATCH 5/5] [translation] Fix pyright enum_type None guard in serializer --- .../azure/ai/translation/text/_serialization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/translation/azure-ai-translation-text/azure/ai/translation/text/_serialization.py b/sdk/translation/azure-ai-translation-text/azure/ai/translation/text/_serialization.py index 70e3b54bc97a..1837b1058b51 100644 --- a/sdk/translation/azure-ai-translation-text/azure/ai/translation/text/_serialization.py +++ b/sdk/translation/azure-ai-translation-text/azure/ai/translation/text/_serialization.py @@ -810,7 +810,7 @@ def serialize_data(self, data, data_type, **kwargs): # If dependencies is empty, try with current data class # It has to be a subclass of Enum anyway enum_type = self.dependencies.get(data_type, data.__class__) - if issubclass(enum_type, Enum): + if enum_type is not None and issubclass(enum_type, Enum): return Serializer.serialize_enum(data, enum_obj=enum_type) iter_type = data_type[0] + data_type[-1]