diff --git a/sdk/translation/azure-ai-translation-document/CHANGELOG.md b/sdk/translation/azure-ai-translation-document/CHANGELOG.md index 5f035cf8118c..1c35dcd375da 100644 --- a/sdk/translation/azure-ai-translation-document/CHANGELOG.md +++ b/sdk/translation/azure-ai-translation-document/CHANGELOG.md @@ -1,11 +1,31 @@ # Release History -## 1.1.1 (Unreleased) +## 2.0.0 (Unreleased) ### Features Added +- Added support for the `2026-03-01` service API version, which is now the default. +- Added image translation support: the `translate_text_within_image` keyword on `begin_translation` + for batch requests, and a `translate_text_within_image` keyword on `SingleDocumentTranslationClient.translate` + for single document requests. When enabled, each document's status also reports image scan usage. +- Added the `deployment_name` property to `TranslationTarget` and the `deployment_name` keyword to + `begin_translation` to specify the deployment name of the custom translation model for a batch + translation request. +- Added the `deployment_name` keyword to `SingleDocumentTranslationClient.translate` for single + document translation requests. +- Added the `deployment_name` property to `DocumentStatus`, exposing the deployment name of the + custom translation model used for the translation. +- Added image scan reporting to `DocumentStatus`: `image_characters_detected`, `images_charged`, + `total_image_scans_succeeded`, and `total_image_scans_failed`. +- Added image scan totals to `TranslationStatusSummary`: `total_image_scans_succeeded`, + `total_image_scans_failed`, and `total_images_charged`. +- Added the `BatchOptions` model for specifying batch translation options. + ### Breaking Changes +- Changed the default service API version from `2024-05-01` to `2026-03-01`. To keep the previous + behavior, pass `api_version="2024-05-01"` when constructing the client. + ### Bugs Fixed ### Other Changes diff --git a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_client.py b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_client.py index 36fb2051321e..6b9531e3c240 100644 --- a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_client.py +++ b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_client.py @@ -33,7 +33,7 @@ class DocumentTranslationClient(DocumentTranslationClientOperationsMixin): AzureKeyCredential type or a TokenCredential type. Required. :type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials.TokenCredential - :keyword api_version: The API version to use for this operation. Default value is "2024-05-01". + :keyword api_version: The API version to use for this operation. Default value is "2026-03-01". Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str :keyword int polling_interval: Default waiting time between two polls for LRO operations if no @@ -113,7 +113,7 @@ class SingleDocumentTranslationClient(SingleDocumentTranslationClientOperationsM AzureKeyCredential type or a TokenCredential type. Required. :type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials.TokenCredential - :keyword api_version: The API version to use for this operation. Default value is "2024-05-01". + :keyword api_version: The API version to use for this operation. Default value is "2026-03-01". Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ diff --git a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_configuration.py b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_configuration.py index df7480f5c50e..7914cfa78edf 100644 --- a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_configuration.py +++ b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_configuration.py @@ -30,13 +30,13 @@ class DocumentTranslationClientConfiguration: # pylint: disable=too-many-instan AzureKeyCredential type or a TokenCredential type. Required. :type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials.TokenCredential - :keyword api_version: The API version to use for this operation. Default value is "2024-05-01". + :keyword api_version: The API version to use for this operation. Default value is "2026-03-01". Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ def __init__(self, endpoint: str, credential: Union[AzureKeyCredential, "TokenCredential"], **kwargs: Any) -> None: - api_version: str = kwargs.pop("api_version", "2024-05-01") + api_version: str = kwargs.pop("api_version", "2026-03-01") if endpoint is None: raise ValueError("Parameter 'endpoint' must not be None.") @@ -85,13 +85,13 @@ class SingleDocumentTranslationClientConfiguration: # pylint: disable=too-many- AzureKeyCredential type or a TokenCredential type. Required. :type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials.TokenCredential - :keyword api_version: The API version to use for this operation. Default value is "2024-05-01". + :keyword api_version: The API version to use for this operation. Default value is "2026-03-01". Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ def __init__(self, endpoint: str, credential: Union[AzureKeyCredential, "TokenCredential"], **kwargs: Any) -> None: - api_version: str = kwargs.pop("api_version", "2024-05-01") + api_version: str = kwargs.pop("api_version", "2026-03-01") if endpoint is None: raise ValueError("Parameter 'endpoint' must not be None.") diff --git a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_operations/_operations.py b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_operations/_operations.py index e86f7ccb4210..0515af537be7 100644 --- a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_operations/_operations.py +++ b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_operations/_operations.py @@ -59,7 +59,7 @@ def build_document_translation__begin_translation_request( # pylint: disable=na _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-05-01")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-03-01")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -91,7 +91,7 @@ def build_document_translation_list_translation_statuses_request( # pylint: dis _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) - api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-05-01")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-03-01")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -132,7 +132,7 @@ def build_document_translation_get_document_status_request( # pylint: disable=n _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) - api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-05-01")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-03-01")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -159,7 +159,7 @@ def build_document_translation_get_translation_status_request( # pylint: disabl _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) - api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-05-01")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-03-01")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -185,7 +185,7 @@ def build_document_translation_cancel_translation_request( # pylint: disable=na _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) - api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-05-01")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-03-01")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -221,7 +221,7 @@ def build_document_translation_list_document_statuses_request( # pylint: disabl _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) - api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-05-01")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-03-01")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -267,7 +267,7 @@ def build_document_translation_get_supported_formats_request( # pylint: disable _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) - api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-05-01")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-03-01")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -289,13 +289,15 @@ def build_single_document_translation_translate_request( # pylint: disable=name target_language: str, source_language: Optional[str] = None, category: Optional[str] = None, + deployment_name: Optional[str] = None, allow_fallback: Optional[bool] = None, + translate_text_within_image: Optional[bool] = None, **kwargs: Any, ) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) - api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-05-01")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-03-01")) accept = _headers.pop("Accept", "application/octet-stream") # Construct URL @@ -308,8 +310,14 @@ def build_single_document_translation_translate_request( # pylint: disable=name _params["targetLanguage"] = _SERIALIZER.query("target_language", target_language, "str") if category is not None: _params["category"] = _SERIALIZER.query("category", category, "str") + if deployment_name is not None: + _params["deploymentName"] = _SERIALIZER.query("deployment_name", deployment_name, "str") if allow_fallback is not None: _params["allowFallback"] = _SERIALIZER.query("allow_fallback", allow_fallback, "bool") + if translate_text_within_image is not None: + _params["translateTextWithinImage"] = _SERIALIZER.query( + "translate_text_within_image", translate_text_within_image, "bool" + ) # Construct headers _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") diff --git a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_operations/_patch.py b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_operations/_patch.py index 0c597a6d71d5..98a3cf210d74 100644 --- a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_operations/_patch.py +++ b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_operations/_patch.py @@ -344,7 +344,9 @@ def translate( target_language: str, source_language: Optional[str] = None, category: Optional[str] = None, + deployment_name: Optional[str] = None, allow_fallback: Optional[bool] = None, + translate_text_within_image: Optional[bool] = None, **kwargs: Any ) -> Iterator[bytes]: """Submit a single document translation request to the Document Translation service. @@ -371,10 +373,16 @@ def translate( project details to this parameter to use your deployed customized system. Default value is: general. Default value is None. :paramtype category: str + :keyword deployment_name: Deployment name of the custom translation model for the translation + request. Default value is None. + :paramtype deployment_name: str :keyword allow_fallback: Specifies that the service is allowed to fall back to a general system when a custom system doesn't exist. Possible values are: true (default) or false. Default value is None. :paramtype allow_fallback: bool + :keyword translate_text_within_image: Optional boolean parameter to translate text within an + image in the document. Default value is None. + :paramtype translate_text_within_image: bool :return: Iterator[bytes] :rtype: Iterator[bytes] :raises ~azure.core.exceptions.HttpResponseError: @@ -397,7 +405,9 @@ def translate( target_language: str, source_language: Optional[str] = None, category: Optional[str] = None, + deployment_name: Optional[str] = None, allow_fallback: Optional[bool] = None, + translate_text_within_image: Optional[bool] = None, **kwargs: Any ) -> Iterator[bytes]: """Submit a single document translation request to the Document Translation service. @@ -424,10 +434,16 @@ def translate( project details to this parameter to use your deployed customized system. Default value is: general. Default value is None. :paramtype category: str + :keyword deployment_name: Deployment name of the custom translation model for the translation + request. Default value is None. + :paramtype deployment_name: str :keyword allow_fallback: Specifies that the service is allowed to fall back to a general system when a custom system doesn't exist. Possible values are: true (default) or false. Default value is None. :paramtype allow_fallback: bool + :keyword translate_text_within_image: Optional boolean parameter to translate text within an + image in the document. Default value is None. + :paramtype translate_text_within_image: bool :return: Iterator[bytes] :rtype: Iterator[bytes] :raises ~azure.core.exceptions.HttpResponseError: @@ -441,7 +457,9 @@ def translate( target_language: str, source_language: Optional[str] = None, category: Optional[str] = None, + deployment_name: Optional[str] = None, allow_fallback: Optional[bool] = None, + translate_text_within_image: Optional[bool] = None, **kwargs: Any ) -> Iterator[bytes]: """Submit a single document translation request to the Document Translation service. @@ -468,10 +486,16 @@ def translate( project details to this parameter to use your deployed customized system. Default value is: general. Default value is None. :paramtype category: str + :keyword deployment_name: Deployment name of the custom translation model for the translation + request. Default value is None. + :paramtype deployment_name: str :keyword allow_fallback: Specifies that the service is allowed to fall back to a general system when a custom system doesn't exist. Possible values are: true (default) or false. Default value is None. :paramtype allow_fallback: bool + :keyword translate_text_within_image: Optional boolean parameter to translate text within an + image in the document. Default value is None. + :paramtype translate_text_within_image: bool :return: Iterator[bytes] :rtype: Iterator[bytes] :raises ~azure.core.exceptions.HttpResponseError: @@ -507,7 +531,9 @@ def translate( target_language=target_language, source_language=source_language, category=category, + deployment_name=deployment_name, allow_fallback=allow_fallback, + translate_text_within_image=translate_text_within_image, api_version=self._config.api_version, files=_files, data=_data, diff --git a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_patch.py b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_patch.py index 00f24ba7de30..009134de5bd0 100644 --- a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_patch.py +++ b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_patch.py @@ -30,6 +30,7 @@ TranslationStatus, DocumentTranslationError, DocumentTranslationInput, + BatchOptions, ) from .models._patch import convert_status @@ -61,9 +62,11 @@ def convert_order_by(orderby: Optional[List[str]]) -> Optional[List[str]]: class DocumentTranslationApiVersion(str, Enum, metaclass=CaseInsensitiveEnumMeta): """Document Translation API versions supported by this package""" - #: This is the default version V2024_05_01 = "2024-05-01" + #: This is the default version + V2026_03_01 = "2026-03-01" + def get_translation_input(args, kwargs, continuation_token): if continuation_token: @@ -102,6 +105,8 @@ def get_translation_input(args, kwargs, continuation_token): suffix = kwargs.pop("suffix", None) storage_type = kwargs.pop("storage_type", None) category_id = kwargs.pop("category_id", None) + deployment_name = kwargs.pop("deployment_name", None) + translate_text_within_image = kwargs.pop("translate_text_within_image", None) glossaries = kwargs.pop("glossaries", None) request = StartTranslationDetails( @@ -118,11 +123,17 @@ def get_translation_input(args, kwargs, continuation_token): language=target_language, glossaries=glossaries, category_id=category_id, + deployment_name=deployment_name, ) ], storage_type=storage_type, ) - ] + ], + options=( + BatchOptions(translate_text_within_image=translate_text_within_image) + if translate_text_within_image is not None + else None + ), ) except (AttributeError, TypeError, IndexError) as exc: raise ValueError( @@ -235,6 +246,8 @@ def begin_translation( storage_type: Optional[Union[str, StorageInputType]] = None, category_id: Optional[str] = None, glossaries: Optional[List[TranslationGlossary]] = None, + deployment_name: Optional[str] = None, + translate_text_within_image: Optional[bool] = None, **kwargs: Any ) -> DocumentTranslationLROPoller[ItemPaged[DocumentStatus]]: """Begin translating the document(s) in your source container to your target container @@ -265,6 +278,10 @@ def begin_translation( :keyword str category_id: Category / custom model ID for using custom translation. :keyword glossaries: Glossaries to apply to translation. :paramtype glossaries: list[~azure.ai.translation.document.TranslationGlossary] + :keyword str deployment_name: Deployment name of the custom translation model for the + translation request. + :keyword bool translate_text_within_image: Whether to translate text embedded within images + in the documents. :return: An instance of a DocumentTranslationLROPoller. Call `result()` on the poller object to return a pageable of DocumentStatus. A DocumentStatus will be returned for each translation on a document. diff --git a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_version.py b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_version.py index 3dcc33575aad..8f2350dd3b0c 100644 --- a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_version.py +++ b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/_version.py @@ -6,4 +6,4 @@ # Changes may cause incorrect behavior and will be lost if the code is regenerated. # -------------------------------------------------------------------------- -VERSION = "1.1.1" +VERSION = "2.0.0" diff --git a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/aio/_client.py b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/aio/_client.py index d8e017fc4f57..7c6e72d9f172 100644 --- a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/aio/_client.py +++ b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/aio/_client.py @@ -33,7 +33,7 @@ class DocumentTranslationClient(DocumentTranslationClientOperationsMixin): AzureKeyCredential type or a TokenCredential type. Required. :type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials_async.AsyncTokenCredential - :keyword api_version: The API version to use for this operation. Default value is "2024-05-01". + :keyword api_version: The API version to use for this operation. Default value is "2026-03-01". Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str :keyword int polling_interval: Default waiting time between two polls for LRO operations if no @@ -117,7 +117,7 @@ class SingleDocumentTranslationClient(SingleDocumentTranslationClientOperationsM AzureKeyCredential type or a TokenCredential type. Required. :type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials_async.AsyncTokenCredential - :keyword api_version: The API version to use for this operation. Default value is "2024-05-01". + :keyword api_version: The API version to use for this operation. Default value is "2026-03-01". Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ diff --git a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/aio/_configuration.py b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/aio/_configuration.py index f0876e7f2d08..f2504925e826 100644 --- a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/aio/_configuration.py +++ b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/aio/_configuration.py @@ -30,7 +30,7 @@ class DocumentTranslationClientConfiguration: # pylint: disable=too-many-instan AzureKeyCredential type or a TokenCredential type. Required. :type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials_async.AsyncTokenCredential - :keyword api_version: The API version to use for this operation. Default value is "2024-05-01". + :keyword api_version: The API version to use for this operation. Default value is "2026-03-01". Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ @@ -38,7 +38,7 @@ class DocumentTranslationClientConfiguration: # pylint: disable=too-many-instan def __init__( self, endpoint: str, credential: Union[AzureKeyCredential, "AsyncTokenCredential"], **kwargs: Any ) -> None: - api_version: str = kwargs.pop("api_version", "2024-05-01") + api_version: str = kwargs.pop("api_version", "2026-03-01") if endpoint is None: raise ValueError("Parameter 'endpoint' must not be None.") @@ -87,7 +87,7 @@ class SingleDocumentTranslationClientConfiguration: # pylint: disable=too-many- AzureKeyCredential type or a TokenCredential type. Required. :type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials_async.AsyncTokenCredential - :keyword api_version: The API version to use for this operation. Default value is "2024-05-01". + :keyword api_version: The API version to use for this operation. Default value is "2026-03-01". Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ @@ -95,7 +95,7 @@ class SingleDocumentTranslationClientConfiguration: # pylint: disable=too-many- def __init__( self, endpoint: str, credential: Union[AzureKeyCredential, "AsyncTokenCredential"], **kwargs: Any ) -> None: - api_version: str = kwargs.pop("api_version", "2024-05-01") + api_version: str = kwargs.pop("api_version", "2026-03-01") if endpoint is None: raise ValueError("Parameter 'endpoint' must not be None.") diff --git a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/aio/_operations/_patch.py b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/aio/_operations/_patch.py index 1961b4fee008..ff7ce5c0c34e 100644 --- a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/aio/_operations/_patch.py +++ b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/aio/_operations/_patch.py @@ -255,7 +255,9 @@ async def translate( target_language: str, source_language: Optional[str] = None, category: Optional[str] = None, + deployment_name: Optional[str] = None, allow_fallback: Optional[bool] = None, + translate_text_within_image: Optional[bool] = None, **kwargs: Any ) -> AsyncIterator[bytes]: """Submit a single document translation request to the Document Translation service. @@ -282,10 +284,16 @@ async def translate( project details to this parameter to use your deployed customized system. Default value is: general. Default value is None. :paramtype category: str + :keyword deployment_name: Deployment name of the custom translation model for the translation + request. Default value is None. + :paramtype deployment_name: str :keyword allow_fallback: Specifies that the service is allowed to fall back to a general system when a custom system doesn't exist. Possible values are: true (default) or false. Default value is None. :paramtype allow_fallback: bool + :keyword translate_text_within_image: Optional boolean parameter to translate text within an + image in the document. Default value is None. + :paramtype translate_text_within_image: bool :return: AsyncIterator[bytes] :rtype: AsyncIterator[bytes] :raises ~azure.core.exceptions.HttpResponseError: @@ -308,7 +316,9 @@ async def translate( target_language: str, source_language: Optional[str] = None, category: Optional[str] = None, + deployment_name: Optional[str] = None, allow_fallback: Optional[bool] = None, + translate_text_within_image: Optional[bool] = None, **kwargs: Any ) -> AsyncIterator[bytes]: """Submit a single document translation request to the Document Translation service. @@ -335,10 +345,16 @@ async def translate( project details to this parameter to use your deployed customized system. Default value is: general. Default value is None. :paramtype category: str + :keyword deployment_name: Deployment name of the custom translation model for the translation + request. Default value is None. + :paramtype deployment_name: str :keyword allow_fallback: Specifies that the service is allowed to fall back to a general system when a custom system doesn't exist. Possible values are: true (default) or false. Default value is None. :paramtype allow_fallback: bool + :keyword translate_text_within_image: Optional boolean parameter to translate text within an + image in the document. Default value is None. + :paramtype translate_text_within_image: bool :return: AsyncIterator[bytes] :rtype: AsyncIterator[bytes] :raises ~azure.core.exceptions.HttpResponseError: @@ -352,7 +368,9 @@ async def translate( target_language: str, source_language: Optional[str] = None, category: Optional[str] = None, + deployment_name: Optional[str] = None, allow_fallback: Optional[bool] = None, + translate_text_within_image: Optional[bool] = None, **kwargs: Any ) -> AsyncIterator[bytes]: """Submit a single document translation request to the Document Translation service. @@ -379,10 +397,16 @@ async def translate( project details to this parameter to use your deployed customized system. Default value is: general. Default value is None. :paramtype category: str + :keyword deployment_name: Deployment name of the custom translation model for the translation + request. Default value is None. + :paramtype deployment_name: str :keyword allow_fallback: Specifies that the service is allowed to fall back to a general system when a custom system doesn't exist. Possible values are: true (default) or false. Default value is None. :paramtype allow_fallback: bool + :keyword translate_text_within_image: Optional boolean parameter to translate text within an + image in the document. Default value is None. + :paramtype translate_text_within_image: bool :return: AsyncIterator[bytes] :rtype: AsyncIterator[bytes] :raises ~azure.core.exceptions.HttpResponseError: @@ -418,7 +442,9 @@ async def translate( target_language=target_language, source_language=source_language, category=category, + deployment_name=deployment_name, allow_fallback=allow_fallback, + translate_text_within_image=translate_text_within_image, api_version=self._config.api_version, files=_files, data=_data, diff --git a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/aio/_patch.py b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/aio/_patch.py index e4d7ef70cc73..420318ca5c5c 100644 --- a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/aio/_patch.py +++ b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/aio/_patch.py @@ -48,7 +48,7 @@ class DocumentTranslationClient(GeneratedDocumentTranslationClient): AzureKeyCredential type or a TokenCredential type. Required. :type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials_async.AsyncTokenCredential - :keyword api_version: The API version to use for this operation. Default value is "2024-05-01". + :keyword api_version: The API version to use for this operation. Default value is "2026-03-01". Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str :keyword int polling_interval: Default waiting time between two polls for LRO operations if no @@ -132,6 +132,8 @@ async def begin_translation( storage_type: Optional[Union[str, StorageInputType]] = None, category_id: Optional[str] = None, glossaries: Optional[List[TranslationGlossary]] = None, + deployment_name: Optional[str] = None, + translate_text_within_image: Optional[bool] = None, **kwargs: Any ) -> AsyncDocumentTranslationLROPoller[AsyncItemPaged[DocumentStatus]]: """Begin translating the document(s) in your source container to your target container @@ -162,6 +164,10 @@ async def begin_translation( :keyword str category_id: Category / custom model ID for using custom translation. :keyword glossaries: Glossaries to apply to translation. :paramtype glossaries: list[~azure.ai.translation.document.TranslationGlossary] + :keyword str deployment_name: Deployment name of the custom translation model for the + translation request. + :keyword bool translate_text_within_image: Whether to translate text embedded within images + in the documents. :return: An instance of an AsyncDocumentTranslationLROPoller. Call `result()` on the poller object to return a pageable of DocumentStatus. A DocumentStatus will be returned for each translation on a document. diff --git a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/models/__init__.py b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/models/__init__.py index d14cd0756fdf..eb762dbc6174 100644 --- a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/models/__init__.py +++ b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/models/__init__.py @@ -7,6 +7,7 @@ # -------------------------------------------------------------------------- from ._models import DocumentBatch +from ._models import BatchOptions from ._models import DocumentFilter from ._patch import DocumentStatus from ._models import DocumentTranslateContent @@ -31,6 +32,7 @@ __all__ = [ "DocumentBatch", + "BatchOptions", "DocumentFilter", "DocumentStatus", "DocumentTranslateContent", diff --git a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/models/_models.py b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/models/_models.py index ab006704ffce..2b5bf0e2854e 100644 --- a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/models/_models.py +++ b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/models/_models.py @@ -59,6 +59,34 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) +class BatchOptions(_model_base.Model): + """Translation job submission options. + + :ivar translate_text_within_image: Translate text embedded within images in the documents. + :vartype translate_text_within_image: bool + """ + + translate_text_within_image: Optional[bool] = rest_field(name="translateTextWithinImage") + """Translate text embedded within images in the documents.""" + + @overload + def __init__( + self, + *, + translate_text_within_image: Optional[bool] = None, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + class DocumentFilter(_model_base.Model): """Document filter. @@ -129,6 +157,17 @@ class DocumentStatus(_model_base.Model): :vartype id: str :ivar characters_charged: Character charged by the API. :vartype characters_charged: int + :ivar total_image_scans_succeeded: Total image scans succeeded. + :vartype total_image_scans_succeeded: int + :ivar total_image_scans_failed: Total image scans failed. + :vartype total_image_scans_failed: int + :ivar images_charged: Images charged by the API. + :vartype images_charged: int + :ivar image_characters_detected: Characters detected within images. + :vartype image_characters_detected: int + :ivar deployment_name: Deployment name of the custom translation model used for the + translation. + :vartype deployment_name: str """ translated_document_url: Optional[str] = rest_field(name="path") @@ -154,6 +193,16 @@ class DocumentStatus(_model_base.Model): """Document Id. Required.""" characters_charged: Optional[int] = rest_field(name="characterCharged") """Character charged by the API.""" + total_image_scans_succeeded: Optional[int] = rest_field(name="totalImageScansSucceeded") + """Total image scans succeeded.""" + total_image_scans_failed: Optional[int] = rest_field(name="totalImageScansFailed") + """Total image scans failed.""" + images_charged: Optional[int] = rest_field(name="imageCharged") + """Images charged by the API.""" + image_characters_detected: Optional[int] = rest_field(name="imageCharacterDetected") + """Characters detected within images.""" + deployment_name: Optional[str] = rest_field(name="deploymentName") + """Deployment name of the custom translation model used for the translation.""" @overload def __init__( @@ -169,6 +218,11 @@ def __init__( translated_document_url: Optional[str] = None, error: Optional["_models.DocumentTranslationError"] = None, characters_charged: Optional[int] = None, + total_image_scans_succeeded: Optional[int] = None, + total_image_scans_failed: Optional[int] = None, + images_charged: Optional[int] = None, + image_characters_detected: Optional[int] = None, + deployment_name: Optional[str] = None, ) -> None: ... @overload @@ -454,16 +508,21 @@ class StartTranslationDetails(_model_base.Model): :ivar inputs: The input list of documents or folders containing documents. Required. :vartype inputs: list[~azure.ai.translation.document.models.DocumentBatch] + :ivar options: Translation job submission options. + :vartype options: ~azure.ai.translation.document.models.BatchOptions """ inputs: List["_models.DocumentBatch"] = rest_field() """The input list of documents or folders containing documents. Required.""" + options: Optional["_models.BatchOptions"] = rest_field() + """Translation job submission options.""" @overload def __init__( self, *, inputs: List["_models.DocumentBatch"], + options: Optional["_models.BatchOptions"] = None, ) -> None: ... @overload @@ -657,6 +716,12 @@ class TranslationStatusSummary(_model_base.Model): :vartype canceled: int :ivar total_characters_charged: Total characters charged by the API. Required. :vartype total_characters_charged: int + :ivar total_image_scans_succeeded: Total image scans succeeded. + :vartype total_image_scans_succeeded: int + :ivar total_image_scans_failed: Total image scans failed. + :vartype total_image_scans_failed: int + :ivar total_images_charged: Total images charged by the API. + :vartype total_images_charged: int """ total: int = rest_field() @@ -673,6 +738,12 @@ class TranslationStatusSummary(_model_base.Model): """Number of cancelled. Required.""" total_characters_charged: int = rest_field(name="totalCharacterCharged") """Total characters charged by the API. Required.""" + total_image_scans_succeeded: Optional[int] = rest_field(name="totalImageScansSucceeded") + """Total image scans succeeded.""" + total_image_scans_failed: Optional[int] = rest_field(name="totalImageScansFailed") + """Total image scans failed.""" + total_images_charged: Optional[int] = rest_field(name="totalImageCharged") + """Total images charged by the API.""" @overload def __init__( @@ -685,6 +756,9 @@ def __init__( not_yet_started: int, canceled: int, total_characters_charged: int, + total_image_scans_succeeded: Optional[int] = None, + total_image_scans_failed: Optional[int] = None, + total_images_charged: Optional[int] = None, ) -> None: ... @overload @@ -707,6 +781,9 @@ class TranslationTarget(_model_base.Model): :vartype target_url: str :ivar category_id: Category / custom system for translation request. :vartype category_id: str + :ivar deployment_name: Deployment name of the custom translation model for the translation + request. + :vartype deployment_name: str :ivar language: Target Language. Required. :vartype language: str :ivar glossaries: List of Glossary. @@ -719,6 +796,8 @@ class TranslationTarget(_model_base.Model): """Location of the folder / container with your documents. Required.""" category_id: Optional[str] = rest_field(name="category") """Category / custom system for translation request.""" + deployment_name: Optional[str] = rest_field(name="deploymentName") + """Deployment name of the custom translation model for the translation request.""" language: str = rest_field() """Target Language. Required.""" glossaries: Optional[List["_models.TranslationGlossary"]] = rest_field() @@ -733,6 +812,7 @@ def __init__( target_url: str, language: str, category_id: Optional[str] = None, + deployment_name: Optional[str] = None, glossaries: Optional[List["_models.TranslationGlossary"]] = None, storage_source: Optional[Union[str, "_models.TranslationStorageSource"]] = None, ) -> None: ... diff --git a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/models/_patch.py b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/models/_patch.py index fc95f5ceb09d..c861304cc45f 100644 --- a/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/models/_patch.py +++ b/sdk/translation/azure-ai-translation-document/azure/ai/translation/document/models/_patch.py @@ -149,6 +149,9 @@ class TranslationTarget(GeneratedTranslationTarget): :vartype target_url: str :ivar category_id: Category / custom system for translation request. :vartype category_id: str + :ivar deployment_name: Deployment name of the custom translation model for the translation + request. + :vartype deployment_name: str :ivar language: Target Language. Required. :vartype language: str :ivar glossaries: List of Glossary. @@ -161,6 +164,8 @@ class TranslationTarget(GeneratedTranslationTarget): """Location of the folder / container with your documents. Required.""" category_id: Optional[str] """Category / custom system for translation request.""" + deployment_name: Optional[str] + """Deployment name of the custom translation model for the translation request.""" language: str """Target Language. Required.""" glossaries: Optional[List["TranslationGlossary"]] @@ -175,6 +180,7 @@ def __init__( language: str, *, category_id: Optional[str] = None, + deployment_name: Optional[str] = None, glossaries: Optional[List["TranslationGlossary"]] = None, storage_source: Optional[Union[str, "_models.TranslationStorageSource"]] = None, ): ... @@ -281,6 +287,17 @@ class DocumentStatus(GeneratedDocumentStatus): :vartype id: str :ivar characters_charged: Character charged by the API. :vartype characters_charged: int + :ivar total_image_scans_succeeded: Total image scans succeeded. + :vartype total_image_scans_succeeded: int + :ivar total_image_scans_failed: Total image scans failed. + :vartype total_image_scans_failed: int + :ivar images_charged: Images charged by the API. + :vartype images_charged: int + :ivar image_characters_detected: Characters detected within images. + :vartype image_characters_detected: int + :ivar deployment_name: Deployment name of the custom translation model used for the + translation. + :vartype deployment_name: str """ translated_document_url: Optional[str] @@ -306,6 +323,16 @@ class DocumentStatus(GeneratedDocumentStatus): """Document Id. Required.""" characters_charged: Optional[int] """Character charged by the API.""" + total_image_scans_succeeded: Optional[int] + """Total image scans succeeded.""" + total_image_scans_failed: Optional[int] + """Total image scans failed.""" + images_charged: Optional[int] + """Images charged by the API.""" + image_characters_detected: Optional[int] + """Characters detected within images.""" + deployment_name: Optional[str] + """Deployment name of the custom translation model used for the translation.""" @overload def __init__( @@ -321,6 +348,11 @@ def __init__( translated_document_url: Optional[str] = None, error: Optional["_models.DocumentTranslationError"] = None, characters_charged: Optional[int] = None, + total_image_scans_succeeded: Optional[int] = None, + total_image_scans_failed: Optional[int] = None, + images_charged: Optional[int] = None, + image_characters_detected: Optional[int] = None, + deployment_name: Optional[str] = None, ): ... @overload diff --git a/sdk/translation/azure-ai-translation-document/samples/README.md b/sdk/translation/azure-ai-translation-document/samples/README.md index c778f3ca4380..88cae6d17d99 100644 --- a/sdk/translation/azure-ai-translation-document/samples/README.md +++ b/sdk/translation/azure-ai-translation-document/samples/README.md @@ -60,6 +60,8 @@ what you can do with the Azure Document Translation client library. |----------------|-------------| |[sample_translation_with_azure_blob.py][begin_translation_with_azure_blob] and [sample_translation_with_azure_blob_async.py][begin_translation_with_azure_blob_async]|Translate documents with upload/download help using Azure Blob Storage| |[sample_translation_with_custom_model.py][sample_translation_with_custom_model] and [sample_translation_with_custom_model_async.py][sample_translation_with_custom_model_async]|Translate documents using a custom model| +|[sample_translation_with_custom_model_deployment.py][sample_translation_with_custom_model_deployment] and [sample_translation_with_custom_model_deployment_async.py][sample_translation_with_custom_model_deployment_async]|Translate documents using a custom translation model deployment name| +|[sample_translation_with_image_translation.py][sample_translation_with_image_translation] and [sample_translation_with_image_translation_async.py][sample_translation_with_image_translation_async]|Translate text embedded within images in your documents| |[sample_begin_translation_with_filters.py][sample_begin_translation_with_filters] and [sample_begin_translation_with_filters_async.py][sample_begin_translation_with_filters_async]|Translate documents under a folder or translate only a specific document| |[sample_list_document_statuses_with_filters.py][sample_list_document_statuses_with_filters] and [sample_list_document_statuses_with_filters_async.py][sample_list_document_statuses_with_filters_async]|List document statuses using filters| |[sample_list_translations_with_filters.py][sample_list_translations_with_filters] and [sample_list_translations_with_filters_async.py][sample_list_translations_with_filters_async]|List translation operations using filters| @@ -83,6 +85,10 @@ what you can do with the Azure Document Translation client library. [list_translations_async]: https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/translation/azure-ai-translation-document/samples/async_samples/sample_list_translations_async.py [sample_translation_with_custom_model]: https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/translation/azure-ai-translation-document/samples/sample_translation_with_custom_model.py [sample_translation_with_custom_model_async]: https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/translation/azure-ai-translation-document/samples/async_samples/sample_translation_with_custom_model_async.py +[sample_translation_with_custom_model_deployment]: https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/translation/azure-ai-translation-document/samples/sample_translation_with_custom_model_deployment.py +[sample_translation_with_custom_model_deployment_async]: https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/translation/azure-ai-translation-document/samples/async_samples/sample_translation_with_custom_model_deployment_async.py +[sample_translation_with_image_translation]: https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/translation/azure-ai-translation-document/samples/sample_translation_with_image_translation.py +[sample_translation_with_image_translation_async]: https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/translation/azure-ai-translation-document/samples/async_samples/sample_translation_with_image_translation_async.py [sample_begin_translation_with_filters]: https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/translation/azure-ai-translation-document/samples/sample_begin_translation_with_filters.py [sample_begin_translation_with_filters_async]: https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/translation/azure-ai-translation-document/samples/async_samples/sample_begin_translation_with_filters_async.py [sample_list_document_statuses_with_filters]: https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/translation/azure-ai-translation-document/samples/sample_list_document_statuses_with_filters.py diff --git a/sdk/translation/azure-ai-translation-document/samples/async_samples/sample_translation_with_custom_model_deployment_async.py b/sdk/translation/azure-ai-translation-document/samples/async_samples/sample_translation_with_custom_model_deployment_async.py new file mode 100644 index 000000000000..18e0cd64ef78 --- /dev/null +++ b/sdk/translation/azure-ai-translation-document/samples/async_samples/sample_translation_with_custom_model_deployment_async.py @@ -0,0 +1,75 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +FILE: sample_translation_with_custom_model_deployment_async.py + +DESCRIPTION: + This sample demonstrates how to route a document translation request through a custom + translation model by specifying the model's deployment name. After the operation completes, + each document's status reports the deployment name that was used. + + To set up your containers for translation and generate SAS tokens to your containers (or files) + with the appropriate permissions, see the README. + +USAGE: + python sample_translation_with_custom_model_deployment_async.py + + Set the environment variables with your own values before running the sample: + 1) AZURE_DOCUMENT_TRANSLATION_ENDPOINT - the endpoint to your Document Translation resource. + 2) AZURE_DOCUMENT_TRANSLATION_KEY - your Document Translation API key. + 3) AZURE_SOURCE_CONTAINER_URL - the container SAS URL to your source container which has the documents + to be translated. + 4) AZURE_TARGET_CONTAINER_URL - the container SAS URL to your target container where the translated documents + will be written. + 5) AZURE_CUSTOM_MODEL_DEPLOYMENT_NAME - the deployment name of your custom translation model. +""" + +import asyncio + + +async def sample_translation_with_custom_model_deployment_async(): + import os + from azure.core.credentials import AzureKeyCredential + from azure.ai.translation.document.aio import DocumentTranslationClient + + endpoint = os.environ["AZURE_DOCUMENT_TRANSLATION_ENDPOINT"] + key = os.environ["AZURE_DOCUMENT_TRANSLATION_KEY"] + source_container_url = os.environ["AZURE_SOURCE_CONTAINER_URL"] + target_container_url = os.environ["AZURE_TARGET_CONTAINER_URL"] + deployment_name = os.environ["AZURE_CUSTOM_MODEL_DEPLOYMENT_NAME"] + + client = DocumentTranslationClient(endpoint, AzureKeyCredential(key)) + + async with client: + # Set the deployment name of your custom translation model on the request. + poller = await client.begin_translation( + source_container_url, + target_container_url, + "es", + deployment_name=deployment_name, + ) + result = await poller.result() + + print(f"Operation status: {poller.details.status}") + print(f"Total number of translations on documents: {poller.details.documents_total_count}") + + async for document in result: + print(f"Document ID: {document.id}") + print(f"Document status: {document.status}") + if document.status == "Succeeded": + print(f"Translated document location: {document.translated_document_url}") + print(f"Deployment name used: {document.deployment_name}") + print(f"Characters charged: {document.characters_charged}\n") + elif document.error: + print(f"Error Code: {document.error.code}, Message: {document.error.message}\n") + + +async def main(): + await sample_translation_with_custom_model_deployment_async() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/sdk/translation/azure-ai-translation-document/samples/async_samples/sample_translation_with_image_translation_async.py b/sdk/translation/azure-ai-translation-document/samples/async_samples/sample_translation_with_image_translation_async.py new file mode 100644 index 000000000000..018848f5f571 --- /dev/null +++ b/sdk/translation/azure-ai-translation-document/samples/async_samples/sample_translation_with_image_translation_async.py @@ -0,0 +1,77 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +FILE: sample_translation_with_image_translation_async.py + +DESCRIPTION: + This sample demonstrates how to start a batch translation that also translates text + embedded within images in your documents by enabling `translate_text_within_image`. + When enabled, each document's status reports image scan usage details. + + To set up your containers for translation and generate SAS tokens to your containers (or files) + with the appropriate permissions, see the README. + +USAGE: + python sample_translation_with_image_translation_async.py + + Set the environment variables with your own values before running the sample: + 1) AZURE_DOCUMENT_TRANSLATION_ENDPOINT - the endpoint to your Document Translation resource. + 2) AZURE_DOCUMENT_TRANSLATION_KEY - your Document Translation API key. + 3) AZURE_SOURCE_CONTAINER_URL - the container SAS URL to your source container which has the documents + to be translated. + 4) AZURE_TARGET_CONTAINER_URL - the container SAS URL to your target container where the translated documents + will be written. +""" + +import asyncio + + +async def sample_translation_with_image_translation_async(): + import os + from azure.core.credentials import AzureKeyCredential + from azure.ai.translation.document.aio import DocumentTranslationClient + + endpoint = os.environ["AZURE_DOCUMENT_TRANSLATION_ENDPOINT"] + key = os.environ["AZURE_DOCUMENT_TRANSLATION_KEY"] + source_container_url = os.environ["AZURE_SOURCE_CONTAINER_URL"] + target_container_url = os.environ["AZURE_TARGET_CONTAINER_URL"] + + client = DocumentTranslationClient(endpoint, AzureKeyCredential(key)) + + async with client: + # Enable translation of text embedded within images for the batch. + poller = await client.begin_translation( + source_container_url, + target_container_url, + "es", + translate_text_within_image=True, + ) + result = await poller.result() + + print(f"Operation status: {poller.details.status}") + print(f"Total number of translations on documents: {poller.details.documents_total_count}") + + async for document in result: + print(f"Document ID: {document.id}") + print(f"Document status: {document.status}") + if document.status == "Succeeded": + print(f"Translated document location: {document.translated_document_url}") + print(f"Characters charged: {document.characters_charged}") + # Image scan usage is reported when image translation is enabled. + print(f"Total image scans succeeded: {document.total_image_scans_succeeded}") + print(f"Total image scans failed: {document.total_image_scans_failed}") + print(f"Images charged: {document.images_charged}") + print(f"Characters detected within images: {document.image_characters_detected}\n") + elif document.error: + print(f"Error Code: {document.error.code}, Message: {document.error.message}\n") + + +async def main(): + await sample_translation_with_image_translation_async() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/sdk/translation/azure-ai-translation-document/samples/sample_translation_with_custom_model_deployment.py b/sdk/translation/azure-ai-translation-document/samples/sample_translation_with_custom_model_deployment.py new file mode 100644 index 000000000000..4b9c97cf7660 --- /dev/null +++ b/sdk/translation/azure-ai-translation-document/samples/sample_translation_with_custom_model_deployment.py @@ -0,0 +1,69 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +FILE: sample_translation_with_custom_model_deployment.py + +DESCRIPTION: + This sample demonstrates how to route a document translation request through a custom + translation model by specifying the model's deployment name. The deployment name can be + supplied for both batch and single document translation. After the operation completes, + each document's status reports the deployment name that was used. + + To set up your containers for translation and generate SAS tokens to your containers (or files) + with the appropriate permissions, see the README. + +USAGE: + python sample_translation_with_custom_model_deployment.py + + Set the environment variables with your own values before running the sample: + 1) AZURE_DOCUMENT_TRANSLATION_ENDPOINT - the endpoint to your Document Translation resource. + 2) AZURE_DOCUMENT_TRANSLATION_KEY - your Document Translation API key. + 3) AZURE_SOURCE_CONTAINER_URL - the container SAS URL to your source container which has the documents + to be translated. + 4) AZURE_TARGET_CONTAINER_URL - the container SAS URL to your target container where the translated documents + will be written. + 5) AZURE_CUSTOM_MODEL_DEPLOYMENT_NAME - the deployment name of your custom translation model. +""" + + +def sample_translation_with_custom_model_deployment(): + import os + from azure.core.credentials import AzureKeyCredential + from azure.ai.translation.document import DocumentTranslationClient + + endpoint = os.environ["AZURE_DOCUMENT_TRANSLATION_ENDPOINT"] + key = os.environ["AZURE_DOCUMENT_TRANSLATION_KEY"] + source_container_url = os.environ["AZURE_SOURCE_CONTAINER_URL"] + target_container_url = os.environ["AZURE_TARGET_CONTAINER_URL"] + deployment_name = os.environ["AZURE_CUSTOM_MODEL_DEPLOYMENT_NAME"] + + client = DocumentTranslationClient(endpoint, AzureKeyCredential(key)) + + # Set the deployment name of your custom translation model on the request. + poller = client.begin_translation( + source_container_url, + target_container_url, + "es", + deployment_name=deployment_name, + ) + result = poller.result() + + print(f"Operation status: {poller.details.status}") + print(f"Total number of translations on documents: {poller.details.documents_total_count}") + + for document in result: + print(f"Document ID: {document.id}") + print(f"Document status: {document.status}") + if document.status == "Succeeded": + print(f"Translated document location: {document.translated_document_url}") + print(f"Deployment name used: {document.deployment_name}") + print(f"Characters charged: {document.characters_charged}\n") + elif document.error: + print(f"Error Code: {document.error.code}, Message: {document.error.message}\n") + + +if __name__ == "__main__": + sample_translation_with_custom_model_deployment() diff --git a/sdk/translation/azure-ai-translation-document/samples/sample_translation_with_image_translation.py b/sdk/translation/azure-ai-translation-document/samples/sample_translation_with_image_translation.py new file mode 100644 index 000000000000..0a2efecb255f --- /dev/null +++ b/sdk/translation/azure-ai-translation-document/samples/sample_translation_with_image_translation.py @@ -0,0 +1,70 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +FILE: sample_translation_with_image_translation.py + +DESCRIPTION: + This sample demonstrates how to start a batch translation that also translates text + embedded within images in your documents by enabling `translate_text_within_image`. + When enabled, each document's status reports image scan usage details. + + To set up your containers for translation and generate SAS tokens to your containers (or files) + with the appropriate permissions, see the README. + +USAGE: + python sample_translation_with_image_translation.py + + Set the environment variables with your own values before running the sample: + 1) AZURE_DOCUMENT_TRANSLATION_ENDPOINT - the endpoint to your Document Translation resource. + 2) AZURE_DOCUMENT_TRANSLATION_KEY - your Document Translation API key. + 3) AZURE_SOURCE_CONTAINER_URL - the container SAS URL to your source container which has the documents + to be translated. + 4) AZURE_TARGET_CONTAINER_URL - the container SAS URL to your target container where the translated documents + will be written. +""" + + +def sample_translation_with_image_translation(): + import os + from azure.core.credentials import AzureKeyCredential + from azure.ai.translation.document import DocumentTranslationClient + + endpoint = os.environ["AZURE_DOCUMENT_TRANSLATION_ENDPOINT"] + key = os.environ["AZURE_DOCUMENT_TRANSLATION_KEY"] + source_container_url = os.environ["AZURE_SOURCE_CONTAINER_URL"] + target_container_url = os.environ["AZURE_TARGET_CONTAINER_URL"] + + client = DocumentTranslationClient(endpoint, AzureKeyCredential(key)) + + # Enable translation of text embedded within images for the batch. + poller = client.begin_translation( + source_container_url, + target_container_url, + "es", + translate_text_within_image=True, + ) + result = poller.result() + + print(f"Operation status: {poller.details.status}") + print(f"Total number of translations on documents: {poller.details.documents_total_count}") + + for document in result: + print(f"Document ID: {document.id}") + print(f"Document status: {document.status}") + if document.status == "Succeeded": + print(f"Translated document location: {document.translated_document_url}") + print(f"Characters charged: {document.characters_charged}") + # Image scan usage is reported when image translation is enabled. + print(f"Total image scans succeeded: {document.total_image_scans_succeeded}") + print(f"Total image scans failed: {document.total_image_scans_failed}") + print(f"Images charged: {document.images_charged}") + print(f"Characters detected within images: {document.image_characters_detected}\n") + elif document.error: + print(f"Error Code: {document.error.code}, Message: {document.error.message}\n") + + +if __name__ == "__main__": + sample_translation_with_image_translation() diff --git a/sdk/translation/azure-ai-translation-document/tests/TestData/test-doc-image.docx b/sdk/translation/azure-ai-translation-document/tests/TestData/test-doc-image.docx new file mode 100644 index 000000000000..a5c220411ae7 Binary files /dev/null and b/sdk/translation/azure-ai-translation-document/tests/TestData/test-doc-image.docx differ diff --git a/sdk/translation/azure-ai-translation-document/tests/test_model_updates.py b/sdk/translation/azure-ai-translation-document/tests/test_model_updates.py index 9d644964c52b..60b9bbbca594 100644 --- a/sdk/translation/azure-ai-translation-document/tests/test_model_updates.py +++ b/sdk/translation/azure-ai-translation-document/tests/test_model_updates.py @@ -11,6 +11,8 @@ TranslationStatusSummary, TranslationGlossary, TranslationStatus, + BatchOptions, + StartTranslationDetails, ) from testcase import DocumentTranslationTest from preparer import ( @@ -208,6 +210,52 @@ def test_translation_status_args(self, **kwargs): translation_status_dict = TranslationStatus(**params) self.validate_translation_status(translation_status_dict) + def test_translation_target_deployment_name(self): + # deployment_name is exposed publicly and serialized to the wire property "deploymentName". + target = TranslationTarget( + target_url="https://t7d8641d8f25ec940prim.blob.core.windows.net/target-67890", + language="es", + deployment_name="my-deployment", + ) + assert target.deployment_name == "my-deployment" + + wire = target.as_dict() + assert wire["deploymentName"] == "my-deployment" + + def test_start_translation_details_translate_text_within_image(self): + # translate_text_within_image is carried on BatchOptions via StartTranslationDetails.options. + options = BatchOptions(translate_text_within_image=True) + assert options.translate_text_within_image is True + + details = StartTranslationDetails(inputs=[], options=options) + wire = details.as_dict() + assert wire["options"]["translateTextWithinImage"] is True + + def test_document_status_deserializes_new_fields(self): + # DocumentStatus exposes the deployment name and image scan usage returned by the service. + payload = { + "path": "https://target/doc.txt", + "sourcePath": "https://source/doc.txt", + "createdDateTimeUtc": "2026-03-01T00:00:00Z", + "lastActionDateTimeUtc": "2026-03-01T00:05:00Z", + "status": "Succeeded", + "to": "es", + "progress": 1.0, + "id": "doc-1", + "characterCharged": 100, + "deploymentName": "my-deployment", + "totalImageScansSucceeded": 6, + "totalImageScansFailed": 1, + "imageCharged": 3, + "imageCharacterDetected": 1257, + } + status = DocumentStatus(payload) + assert status.deployment_name == "my-deployment" + assert status.total_image_scans_succeeded == 6 + assert status.total_image_scans_failed == 1 + assert status.images_charged == 3 + assert status.image_characters_detected == 1257 + def validate_translation_target(self, translation_target): assert translation_target is not None assert translation_target.target_url is not None diff --git a/sdk/translation/azure-ai-translation-document/tests/test_translation.py b/sdk/translation/azure-ai-translation-document/tests/test_translation.py index 3407d20ce0f8..d5576611b9f4 100644 --- a/sdk/translation/azure-ai-translation-document/tests/test_translation.py +++ b/sdk/translation/azure-ai-translation-document/tests/test_translation.py @@ -26,6 +26,8 @@ DocumentTranslationClientPreparer = functools.partial(_DocumentTranslationClientPreparer, DocumentTranslationClient) GLOSSARY_FILE_NAME = os.path.abspath(os.path.join(os.path.abspath(__file__), "..", "./glossaries-valid.csv")) +# A document that embeds an image containing legible text, used to exercise image translation. +IMAGE_DOC_FILE_NAME = os.path.abspath(os.path.join(os.path.dirname(__file__), "TestData", "test-doc-image.docx")) class TestTranslation(DocumentTranslationTest): @@ -503,6 +505,61 @@ def test_single_input_with_kwarg_successful(self, **kwargs): self._validate_doc_status(doc, target_language="fr") return variables + @DocumentTranslationPreparer() + @DocumentTranslationClientPreparer() + @recorded_by_proxy + def test_single_source_single_target_with_deployment_name(self, **kwargs): + client = kwargs.pop("client") + variables = kwargs.pop("variables", {}) + # prepare containers and test data + blob_data = b"This is some text" + source_container_sas_url = self.create_source_container(data=Document(data=blob_data), variables=variables) + target_container_sas_url = self.create_target_container(variables=variables) + + # "general" routes to the default NMT model, so no custom model deployment is required. + translation_inputs = [ + DocumentTranslationInput( + source_url=source_container_sas_url, + targets=[ + TranslationTarget(target_url=target_container_sas_url, language="fr", deployment_name="general") + ], + ) + ] + + # The service accepts the deployment name and translates successfully. The document status does + # not echo the deployment name back for the default ("general") model, so request-side + # serialization of deployment_name is verified separately in the model tests. + self._begin_and_validate_translation(client, translation_inputs, 1, "fr") + return variables + + @DocumentTranslationPreparer() + @DocumentTranslationClientPreparer() + @recorded_by_proxy + def test_single_source_single_target_with_image_translation(self, **kwargs): + client = kwargs.pop("client") + variables = kwargs.pop("variables", {}) + # The document must contain an image with legible text so image scanning has something to translate. + with open(IMAGE_DOC_FILE_NAME, "rb") as fd: + image_doc_data = fd.read() + source_container_sas_url = self.create_source_container( + data=Document(name="test-doc-image", suffix=".docx", data=image_doc_data), variables=variables + ) + target_container_sas_url = self.create_target_container(variables=variables) + + # Enable translation of text embedded within images for the batch. + poller = client.begin_translation( + source_container_sas_url, target_container_sas_url, "fr", translate_text_within_image=True + ) + result = poller.result() + + self._validate_translation_metadata(poller, status="Succeeded", total=1, succeeded=1) + for doc in result: + # The request enables image translation (verified on the wire) and the document translates + # successfully. Image scan usage is only reported by the service when images are actually + # scanned, so it is not asserted here. + self._validate_doc_status(doc, target_language="fr") + return variables + @pytest.mark.skip("Flaky test") @pytest.mark.live_test_only @DocumentTranslationPreparer() @@ -513,12 +570,13 @@ def test_translation_with_glossary(self, **kwargs): source_container_sas_url = self.create_source_container(data=[doc]) target_container_sas_url = self.create_target_container() - container_client = ContainerClient(self.storage_endpoint, self.source_container_name, self.storage_key) + container_client = ContainerClient( + self.storage_endpoint, self.source_container_name, credential=self.get_storage_credential() + ) with open(GLOSSARY_FILE_NAME, "rb") as fd: container_client.upload_blob(name=GLOSSARY_FILE_NAME, data=fd.read()) - prefix, suffix = source_container_sas_url.split("?") - glossary_file_sas_url = prefix + "/" + GLOSSARY_FILE_NAME + "?" + suffix + glossary_file_sas_url = source_container_sas_url + "/" + GLOSSARY_FILE_NAME poller = client.begin_translation( source_container_sas_url, @@ -528,7 +586,9 @@ def test_translation_with_glossary(self, **kwargs): ) result = poller.result() - container_client = ContainerClient(self.storage_endpoint, self.target_container_name, self.storage_key) + container_client = ContainerClient( + self.storage_endpoint, self.target_container_name, credential=self.get_storage_credential() + ) # download translated file and assert that translation reflects glossary changes document = doc.name + doc.suffix diff --git a/sdk/translation/azure-ai-translation-document/tests/test_translation_async.py b/sdk/translation/azure-ai-translation-document/tests/test_translation_async.py index 30f2fe07adbe..eff51499e39b 100644 --- a/sdk/translation/azure-ai-translation-document/tests/test_translation_async.py +++ b/sdk/translation/azure-ai-translation-document/tests/test_translation_async.py @@ -23,6 +23,8 @@ DocumentTranslationClientPreparer = functools.partial(_DocumentTranslationClientPreparer, DocumentTranslationClient) GLOSSARY_FILE_NAME = os.path.abspath(os.path.join(os.path.abspath(__file__), "..", "./glossaries-valid.csv")) +# A document that embeds an image containing legible text, used to exercise image translation. +IMAGE_DOC_FILE_NAME = os.path.abspath(os.path.join(os.path.dirname(__file__), "TestData", "test-doc-image.docx")) class TestTranslation(AsyncDocumentTranslationTest): @@ -510,6 +512,62 @@ async def test_single_input_with_kwarg_successful(self, **kwargs): self._validate_doc_status(doc, target_language="fr") return variables + @DocumentTranslationPreparer() + @DocumentTranslationClientPreparer() + @recorded_by_proxy_async + async def test_single_source_single_target_with_deployment_name(self, **kwargs): + client = kwargs.pop("client") + variables = kwargs.pop("variables", {}) + # prepare containers and test data + blob_data = b"This is some text" + source_container_sas_url = self.create_source_container(data=Document(data=blob_data), variables=variables) + target_container_sas_url = self.create_target_container(variables=variables) + + # "general" routes to the default NMT model, so no custom model deployment is required. + translation_inputs = [ + DocumentTranslationInput( + source_url=source_container_sas_url, + targets=[ + TranslationTarget(target_url=target_container_sas_url, language="fr", deployment_name="general") + ], + ) + ] + + # The service accepts the deployment name and translates successfully. The document status does + # not echo the deployment name back for the default ("general") model, so request-side + # serialization of deployment_name is verified separately in the model tests. + await self._begin_and_validate_translation_async(client, translation_inputs, 1, "fr") + return variables + + @DocumentTranslationPreparer() + @DocumentTranslationClientPreparer() + @recorded_by_proxy_async + async def test_single_source_single_target_with_image_translation(self, **kwargs): + client = kwargs.pop("client") + variables = kwargs.pop("variables", {}) + # The document must contain an image with legible text so image scanning has something to translate. + with open(IMAGE_DOC_FILE_NAME, "rb") as fd: + image_doc_data = fd.read() + source_container_sas_url = self.create_source_container( + data=Document(name="test-doc-image", suffix=".docx", data=image_doc_data), variables=variables + ) + target_container_sas_url = self.create_target_container(variables=variables) + + async with client: + # Enable translation of text embedded within images for the batch. + poller = await client.begin_translation( + source_container_sas_url, target_container_sas_url, "fr", translate_text_within_image=True + ) + result = await poller.result() + + self._validate_translation_metadata(poller, status="Succeeded", total=1, succeeded=1) + async for doc in result: + # The request enables image translation (verified on the wire) and the document translates + # successfully. Image scan usage is only reported by the service when images are actually + # scanned, so it is not asserted here. + self._validate_doc_status(doc, target_language="fr") + return variables + @pytest.mark.skip("Flaky test") @pytest.mark.live_test_only @DocumentTranslationPreparer() @@ -520,12 +578,13 @@ async def test_translation_with_glossary(self, **kwargs): source_container_sas_url = self.create_source_container(data=[doc]) target_container_sas_url = self.create_target_container() - container_client = ContainerClient(self.storage_endpoint, self.source_container_name, self.storage_key) + container_client = ContainerClient( + self.storage_endpoint, self.source_container_name, credential=self.get_storage_credential() + ) with open(GLOSSARY_FILE_NAME, "rb") as fd: container_client.upload_blob(name=GLOSSARY_FILE_NAME, data=fd.read()) - prefix, suffix = source_container_sas_url.split("?") - glossary_file_sas_url = prefix + "/" + GLOSSARY_FILE_NAME + "?" + suffix + glossary_file_sas_url = source_container_sas_url + "/" + GLOSSARY_FILE_NAME async with client: poller = await client.begin_translation( source_container_sas_url, @@ -535,7 +594,9 @@ async def test_translation_with_glossary(self, **kwargs): ) result = await poller.result() - container_client = ContainerClient(self.storage_endpoint, self.target_container_name, self.storage_key) + container_client = ContainerClient( + self.storage_endpoint, self.target_container_name, credential=self.get_storage_credential() + ) # download translated file and assert that translation reflects glossary changes document = doc.name + doc.suffix diff --git a/sdk/translation/azure-ai-translation-document/tests/testcase.py b/sdk/translation/azure-ai-translation-document/tests/testcase.py index 8d2abead3989..93389359e080 100644 --- a/sdk/translation/azure-ai-translation-document/tests/testcase.py +++ b/sdk/translation/azure-ai-translation-document/tests/testcase.py @@ -5,11 +5,11 @@ import os import time -import datetime import uuid from azure.ai.translation.document.models import DocumentBatch, SourceInput, StartTranslationDetails from devtools_testutils import AzureRecordedTestCase, set_custom_default_matcher -from azure.storage.blob import generate_container_sas, ContainerClient +from azure.storage.blob import ContainerClient +from azure.identity import DefaultAzureCredential from azure.ai.translation.document import DocumentTranslationInput, TranslationTarget @@ -39,9 +39,11 @@ def storage_name(self): def storage_endpoint(self): return "https://" + self.storage_name + ".blob.core.windows.net/" - @property - def storage_key(self): - return os.getenv("DOCUMENT_TRANSLATION_STORAGE_KEY", "fakeZmFrZV9hY29jdW50X2tleQ==") + def get_storage_credential(self): + # Recorded tests authenticate to storage with Entra ID (RBAC) rather than an + # account key, so they work even when the storage account has shared-key access + # disabled. Only used in live/record mode; playback never touches storage. + return DefaultAzureCredential() def upload_documents(self, data, container_client): if isinstance(data, list): @@ -55,40 +57,37 @@ def create_source_container(self, data, variables={}, **kwargs): var_key = "source_container_name" + container_suffix if self.is_live: self.source_container_name = variables[var_key] = "src" + str(uuid.uuid4()) - container_client = ContainerClient(self.storage_endpoint, variables[var_key], self.storage_key) + container_client = ContainerClient( + self.storage_endpoint, variables[var_key], credential=self.get_storage_credential() + ) container_client.create_container() self.upload_documents(data, container_client) - return self.generate_sas_url(variables[var_key], "rl") + return self.container_url(variables[var_key]) def create_target_container(self, data=None, variables={}, **kwargs): container_suffix = kwargs.get("container_suffix", "") var_key = "target_container_name" + container_suffix if self.is_live: self.target_container_name = variables[var_key] = "target" + str(uuid.uuid4()) - container_client = ContainerClient(self.storage_endpoint, variables[var_key], self.storage_key) + container_client = ContainerClient( + self.storage_endpoint, variables[var_key], credential=self.get_storage_credential() + ) container_client.create_container() if data: self.upload_documents(data, container_client) - return self.generate_sas_url(variables[var_key], "wl") + return self.container_url(variables[var_key]) - def generate_sas_url(self, container_name, permission): - # this can be reverted to set_bodiless_matcher() after tests are re-recorded and don't contain these headers + def container_url(self, container_name): + # The Document Translation service reads/writes the containers using the Translator + # resource's managed identity, so a plain container URL (no SAS) is passed. This + # avoids any dependency on the storage account key / shared-key access. + # This can be reverted to set_bodiless_matcher() after tests are re-recorded. set_custom_default_matcher( compare_bodies=False, excluded_headers="Authorization,Content-Length,x-ms-client-request-id,x-ms-request-id" ) - sas_token = self.generate_sas( - generate_container_sas, - account_name=self.storage_name, - container_name=container_name, - account_key=self.storage_key, - permission=permission, - expiry=datetime.datetime.utcnow() + datetime.timedelta(hours=2), - ) - - container_sas_url = self.storage_endpoint + container_name + "?" + sas_token - return container_sas_url + return self.storage_endpoint + container_name def wait(self, duration=30): if self.is_live: diff --git a/sdk/translation/azure-ai-translation-document/tsp-location.yaml b/sdk/translation/azure-ai-translation-document/tsp-location.yaml index bb4b6ef00eab..b3d8d7ac8a98 100644 --- a/sdk/translation/azure-ai-translation-document/tsp-location.yaml +++ b/sdk/translation/azure-ai-translation-document/tsp-location.yaml @@ -1,3 +1,3 @@ -commit: ccc08b40afbff1abe17c8250ed03a87e81fbf673 +commit: dab7850eee3d3f2415ae6799b53de94aa8ec0e95 repo: Azure/azure-rest-api-specs -directory: specification/translation/Azure.AI.DocumentTranslation +directory: specification/translation/data-plane/DocumentTranslation