Skip to content

Commit 8f5fb10

Browse files
[formrecognizer] support Copy API (Azure#11372)
* add copy feature gen code * add copy implementation (wip) * update copy implementation * add copy model samples * add back get copy auth method and update samples * add CopyAuthorization to init * changes from feedback on design * raise better error messages in polling for failed copying * update readme/changelog * updating sample snippets * renames to align with .net * change copy target to dict * add copy sync/async tests * make mypy happy * update docstrings * review feedback * rename authorize_copy_target -> get_copy_authorization * feedback + add test for copy authorization * fix to testcase * change description in samples * hardcode region * construct resource id in testcase
1 parent 0667e82 commit 8f5fb10

37 files changed

+3238
-67
lines changed

sdk/formrecognizer/azure-ai-formrecognizer/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ For recognize receipt methods, parameters have been renamed to `receipt` and `re
2121

2222
**New features**
2323

24+
- Support to copy a custom model from one Form Recognizer resource to another
2425
- Authentication using `azure-identity` credentials now supported
2526
- see the [Azure Identity documentation](https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/identity/azure-identity/README.md) for more information
2627

sdk/formrecognizer/azure-ai-formrecognizer/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ See the full details regarding [authentication][cognitive_authentication] of cog
131131
- Training custom models to recognize all fields and values found in your custom forms. A `CustomFormModel` is returned indicating the form types the model will recognize, and the fields it will extract for each form type. See the [service's documents][fr-train-without-labels] for a more detailed explanation.
132132
- Training custom models to recognize specific fields and values you specify by labeling your custom forms. A `CustomFormModel` is returned indicating the fields the model will extract, as well as the estimated accuracy for each field. See the [service's documents][fr-train-with-labels] for a more detailed explanation.
133133
- Managing models created in your account.
134+
- Copying a custom model from one Form Recognizer resource to another.
134135

135136
Please note that models can also be trained using a graphical user interface such as the [Form Recognizer Labeling Tool][fr-labeling-tool].
136137

@@ -389,6 +390,7 @@ with Form Recognizer and require Python 3.5 or later.
389390
* Train a model without labels: [sample_train_model_without_labels.py][sample_train_model_without_labels] ([async version][sample_train_model_without_labels_async])
390391
* Train a model with labels: [sample_train_model_with_labels.py][sample_train_model_with_labels] ([async version][sample_train_model_with_labels_async])
391392
* Manage custom models: [sample_manage_custom_models.py][sample_manage_custom_models] ([async_version][sample_manage_custom_models_async])
393+
* Copy a model between Form Recognizer resources: [sample_copy_model.py][sample_copy_model] ([async_version][sample_copy_model_async])
392394

393395
### Additional documentation
394396

@@ -459,3 +461,5 @@ This project has adopted the [Microsoft Open Source Code of Conduct][code_of_con
459461
[sample_train_model_with_labels_async]: https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/formrecognizer/azure-ai-formrecognizer/samples/async_samples/sample_train_model_with_labels_async.py
460462
[sample_train_model_without_labels]: https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/formrecognizer/azure-ai-formrecognizer/samples/sample_train_model_without_labels.py
461463
[sample_train_model_without_labels_async]: https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/formrecognizer/azure-ai-formrecognizer/samples/async_samples/sample_train_model_without_labels_async.py
464+
[sample_copy_model]: https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/formrecognizer/azure-ai-formrecognizer/samples/sample_copy_model.py
465+
[sample_copy_model_async]: https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/formrecognizer/azure-ai-formrecognizer/samples/async_samples/sample_copy_model_async.py

sdk/formrecognizer/azure-ai-formrecognizer/azure/ai/formrecognizer/_form_training_client.py

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,39 @@
66

77
# pylint: disable=protected-access
88

9+
import json
910
from typing import (
1011
Optional,
1112
Any,
1213
Iterable,
14+
Dict,
1315
Union,
1416
TYPE_CHECKING,
1517
)
1618
from azure.core.tracing.decorator import distributed_trace
1719
from azure.core.polling import LROPoller
1820
from azure.core.polling.base_polling import LROBasePolling
19-
from ._generated.models import Model
2021
from ._generated._form_recognizer_client import FormRecognizerClient as FormRecognizer
21-
from ._generated.models import TrainRequest, TrainSourceFilter
22+
from ._generated.models import (
23+
TrainRequest,
24+
TrainSourceFilter,
25+
CopyRequest,
26+
Model,
27+
CopyOperationResult,
28+
CopyAuthorizationResult
29+
)
2230
from ._helpers import error_map, get_authentication_policy, POLLING_INTERVAL
2331
from ._models import (
2432
CustomFormModelInfo,
2533
AccountProperties,
2634
CustomFormModel
2735
)
28-
from ._polling import TrainingPolling
36+
from ._polling import TrainingPolling, CopyPolling
2937
from ._user_agent import USER_AGENT
3038
from ._form_recognizer_client import FormRecognizerClient
3139
if TYPE_CHECKING:
3240
from azure.core.credentials import AzureKeyCredential, TokenCredential
41+
from azure.core.pipeline import PipelineResponse
3342
from azure.core.pipeline.transport import HttpResponse
3443
PipelineResponseType = HttpResponse
3544

@@ -239,6 +248,99 @@ def get_custom_model(self, model_id, **kwargs):
239248
response = self._client.get_custom_model(model_id=model_id, include_keys=True, error_map=error_map, **kwargs)
240249
return CustomFormModel._from_generated(response)
241250

251+
@distributed_trace
252+
def get_copy_authorization(self, resource_id, resource_region, **kwargs):
253+
# type: (str, str, Any) -> Dict[str, Union[str, int]]
254+
"""Generate authorization for copying a custom model into the target Form Recognizer resource.
255+
This should be called by the target resource (where the model will be copied to)
256+
and the output can be passed as the `target` parameter into :func:`~begin_copy_model()`.
257+
258+
:param str resource_id: Azure Resource Id of the target Form Recognizer resource
259+
where the model will be copied to.
260+
:param str resource_region: Location of the target Form Recognizer resource. A valid Azure
261+
region name supported by Cognitive Services.
262+
:return: A dictionary with values for the copy authorization -
263+
"modelId", "accessToken", "resourceId", "resourceRegion", and "expirationDateTimeTicks".
264+
:rtype: Dict[str, Union[str, int]]
265+
:raises ~azure.core.exceptions.HttpResponseError:
266+
267+
.. admonition:: Example:
268+
269+
.. literalinclude:: ../samples/sample_copy_model.py
270+
:start-after: [START get_copy_authorization]
271+
:end-before: [END get_copy_authorization]
272+
:language: python
273+
:dedent: 8
274+
:caption: Authorize the target resource to receive the copied model
275+
"""
276+
277+
response = self._client.generate_model_copy_authorization( # type: ignore
278+
cls=lambda pipeline_response, deserialized, response_headers: pipeline_response,
279+
error_map=error_map,
280+
**kwargs
281+
) # type: PipelineResponse
282+
target = json.loads(response.http_response.text())
283+
target["resourceId"] = resource_id
284+
target["resourceRegion"] = resource_region
285+
return target
286+
287+
@distributed_trace
288+
def begin_copy_model(
289+
self,
290+
model_id, # type: str
291+
target, # type: Dict
292+
**kwargs # type: Any
293+
):
294+
# type: (...) -> LROPoller
295+
"""Copy a custom model stored in this resource (the source) to the user specified
296+
target Form Recognizer resource. This should be called with the source Form Recognizer resource
297+
(with the model that is intended to be copied). The `target` parameter should be supplied from the
298+
target resource's output from calling the :func:`~get_copy_authorization()` method.
299+
300+
:param str model_id: Model identifier of the model to copy to target resource.
301+
:param dict target:
302+
The copy authorization generated from the target resource's call to
303+
:func:`~get_copy_authorization()`.
304+
:keyword int polling_interval: Default waiting time between two polls for LRO operations if
305+
no Retry-After header is present.
306+
:return: An instance of an LROPoller. Call `result()` on the poller
307+
object to return a :class:`~azure.ai.formrecognizer.CustomFormModelInfo`.
308+
:rtype: ~azure.core.polling.LROPoller[~azure.ai.formrecognizer.CustomFormModelInfo]
309+
:raises ~azure.core.exceptions.HttpResponseError:
310+
311+
.. admonition:: Example:
312+
313+
.. literalinclude:: ../samples/sample_copy_model.py
314+
:start-after: [START begin_copy_model]
315+
:end-before: [END begin_copy_model]
316+
:language: python
317+
:dedent: 8
318+
:caption: Copy a model from the source resource to the target resource
319+
"""
320+
321+
polling_interval = kwargs.pop("polling_interval", POLLING_INTERVAL)
322+
323+
def _copy_callback(raw_response, _, headers): # pylint: disable=unused-argument
324+
copy_result = self._client._deserialize(CopyOperationResult, raw_response)
325+
return CustomFormModelInfo._from_generated(copy_result, target["modelId"])
326+
327+
return self._client.begin_copy_custom_model( # type: ignore
328+
model_id=model_id,
329+
copy_request=CopyRequest(
330+
target_resource_id=target["resourceId"],
331+
target_resource_region=target["resourceRegion"],
332+
copy_authorization=CopyAuthorizationResult(
333+
access_token=target["accessToken"],
334+
model_id=target["modelId"],
335+
expiration_date_time_ticks=target["expirationDateTimeTicks"]
336+
)
337+
),
338+
cls=kwargs.pop("cls", _copy_callback),
339+
polling=LROBasePolling(timeout=polling_interval, lro_algorithms=[CopyPolling()], **kwargs),
340+
error_map=error_map,
341+
**kwargs
342+
)
343+
242344
def get_form_recognizer_client(self, **kwargs):
243345
# type: (Any) -> FormRecognizerClient
244346
"""Get an instance of a FormRecognizerClient from FormTrainingClient.

sdk/formrecognizer/azure-ai-formrecognizer/azure/ai/formrecognizer/_generated/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# coding=utf-8
22
# --------------------------------------------------------------------------
3-
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.0.6246, generator: {generator})
3+
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.0.6282, generator: {generator})
44
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
55
# --------------------------------------------------------------------------
66

sdk/formrecognizer/azure-ai-formrecognizer/azure/ai/formrecognizer/_generated/_configuration.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# coding=utf-8
22
# --------------------------------------------------------------------------
3-
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.0.6246, generator: {generator})
3+
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.0.6282, generator: {generator})
44
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
55
# --------------------------------------------------------------------------
66

@@ -46,6 +46,7 @@ def __init__(
4646
self.credential = credential
4747
self.endpoint = endpoint
4848
self.credential_scopes = ['https://cognitiveservices.azure.com/.default']
49+
self.credential_scopes.extend(kwargs.pop('credential_scopes', []))
4950
kwargs.setdefault('sdk_moniker', 'ai-formrecognizer/{}'.format(VERSION))
5051
self._configure(**kwargs)
5152

sdk/formrecognizer/azure-ai-formrecognizer/azure/ai/formrecognizer/_generated/_form_recognizer_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# coding=utf-8
22
# --------------------------------------------------------------------------
3-
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.0.6246, generator: {generator})
3+
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.0.6282, generator: {generator})
44
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
55
# --------------------------------------------------------------------------
66

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# coding=utf-8
22
# --------------------------------------------------------------------------
3-
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.0.6246, generator: {generator})
3+
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.0.6282, generator: {generator})
44
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
55
# --------------------------------------------------------------------------
66

7-
VERSION = "1.0.0b1"
7+
VERSION = "1.0.0b2"

sdk/formrecognizer/azure-ai-formrecognizer/azure/ai/formrecognizer/_generated/aio/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# coding=utf-8
22
# --------------------------------------------------------------------------
3-
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.0.6246, generator: {generator})
3+
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.0.6282, generator: {generator})
44
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
55
# --------------------------------------------------------------------------
66

sdk/formrecognizer/azure-ai-formrecognizer/azure/ai/formrecognizer/_generated/aio/_configuration_async.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# coding=utf-8
22
# --------------------------------------------------------------------------
3-
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.0.6246, generator: {generator})
3+
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.0.6282, generator: {generator})
44
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
55
# --------------------------------------------------------------------------
66

@@ -43,6 +43,7 @@ def __init__(
4343
self.credential = credential
4444
self.endpoint = endpoint
4545
self.credential_scopes = ['https://cognitiveservices.azure.com/.default']
46+
self.credential_scopes.extend(kwargs.pop('credential_scopes', []))
4647
kwargs.setdefault('sdk_moniker', 'ai-formrecognizer/{}'.format(VERSION))
4748
self._configure(**kwargs)
4849

sdk/formrecognizer/azure-ai-formrecognizer/azure/ai/formrecognizer/_generated/aio/_form_recognizer_client_async.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# coding=utf-8
22
# --------------------------------------------------------------------------
3-
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.0.6246, generator: {generator})
3+
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.0.6282, generator: {generator})
44
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
55
# --------------------------------------------------------------------------
66

sdk/formrecognizer/azure-ai-formrecognizer/azure/ai/formrecognizer/_generated/aio/operations_async/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# coding=utf-8
22
# --------------------------------------------------------------------------
3-
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.0.6246, generator: {generator})
3+
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.0.6282, generator: {generator})
44
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
55
# --------------------------------------------------------------------------
66

0 commit comments

Comments
 (0)