Skip to content

Commit 146bc86

Browse files
[formrecognizer] consistency on handling LRO's with failed status (#11445)
* samples handle invalid models from training * update to throw exception on training methods that return invalid model * update tests now that we treat invalid status differently * pass response to error
1 parent 0160912 commit 146bc86

10 files changed

+30
-23
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- `list_model_infos` method has been renamed to `list_custom_models`
1010
- Removed `get_form_training_client` from `FormRecognizerClient`
1111
- Added `get_form_recognizer_client` to `FormTrainingClient`
12+
- A `HttpResponseError` is now raised if a model with `status=="invalid"` is returned from the `begin_train_model()` or `train_model()` methods
1213

1314
**New features**
1415

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ def begin_train_model(self, training_files_url, use_training_labels=False, **kwa
101101
object to return a :class:`~azure.ai.formrecognizer.CustomFormModel`.
102102
:rtype: ~azure.core.polling.LROPoller[~azure.ai.formrecognizer.CustomFormModel]
103103
:raises ~azure.core.exceptions.HttpResponseError:
104+
Note that if the training fails, the exception is raised, but a model with an
105+
"invalid" status is still created. You can delete this model by calling :func:`~delete_model()`
104106
105107
.. admonition:: Example:
106108

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717
from azure.core.pipeline import PipelineResponse
1818

1919

20+
def raise_error(response, errors, message):
21+
for err in errors:
22+
message += "({}) {}\n".format(err["code"], err["message"])
23+
raise HttpResponseError(message=message, response=response)
24+
25+
2026
class TrainingPolling(LocationPolling):
2127
"""Polling method overrides for training endpoints.
2228
"""
@@ -40,6 +46,14 @@ def get_status(self, pipeline_response): # pylint: disable=no-self-use
4046
status = body['modelInfo']['status']
4147
if not status:
4248
raise BadResponse("No status found in body")
49+
if status.lower() == "invalid":
50+
train_result = body.get('trainResult')
51+
if train_result:
52+
errors = train_result.get("errors")
53+
if errors:
54+
message = "Invalid model created with ID={}\n".format(body["modelInfo"]["modelId"])
55+
raise_error(response, errors, message)
56+
return "Failed"
4357
if status.lower() != "creating":
4458
return "Succeeded"
4559

@@ -50,8 +64,6 @@ def get_status(self, pipeline_response): # pylint: disable=no-self-use
5064

5165
class AnalyzePolling(OperationResourcePolling):
5266
"""Polling method overrides for custom analyze endpoints.
53-
54-
:param str operation_location_header: Name of the header to return operation format (default 'operation-location')
5567
"""
5668

5769
def get_status(self, pipeline_response): # pylint: disable=no-self-use
@@ -78,8 +90,5 @@ def get_status(self, pipeline_response): # pylint: disable=no-self-use
7890
if analyze_result:
7991
errors = analyze_result.get("errors")
8092
if errors:
81-
message = ""
82-
for err in errors:
83-
message += "({}) {}\n".format(err.get("code"), err.get("message"))
84-
raise HttpResponseError(message)
93+
raise_error(response, errors, message="")
8594
return status

sdk/formrecognizer/azure-ai-formrecognizer/azure/ai/formrecognizer/aio/_form_training_client_async.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ async def train_model(
109109
:return: CustomFormModel
110110
:rtype: ~azure.ai.formrecognizer.CustomFormModel
111111
:raises ~azure.core.exceptions.HttpResponseError:
112+
Note that if the training fails, the exception is raised, but a model with an
113+
"invalid" status is still created. You can delete this model by calling :func:`~delete_model()`
112114
113115
.. admonition:: Example:
114116

sdk/formrecognizer/azure-ai-formrecognizer/samples/async_samples/sample_get_bounding_boxes_async.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,14 @@ async def get_bounding_boxes(self):
4141
# the sample forms are located in this file's parent's parent's files.
4242
path_to_sample_forms = Path(__file__).parent.parent.absolute() / Path("sample_forms/forms/Form_1.jpg")
4343
from azure.ai.formrecognizer import FormWord, FormLine
44-
# [START create_form_recognizer_client_async]
4544
from azure.core.credentials import AzureKeyCredential
4645
from azure.ai.formrecognizer.aio import FormRecognizerClient
4746

4847
form_recognizer_client = FormRecognizerClient(
4948
endpoint=self.endpoint, credential=AzureKeyCredential(self.key)
5049
)
51-
# [END create_form_recognizer_client_async]
52-
async with form_recognizer_client:
5350

51+
async with form_recognizer_client:
5452
# Make sure your form's type is included in the list of form types the custom model can recognize
5553
with open(path_to_sample_forms, "rb") as f:
5654
forms = await form_recognizer_client.recognize_custom_forms(

sdk/formrecognizer/azure-ai-formrecognizer/samples/async_samples/sample_train_model_with_labels_async.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,16 @@ class TrainModelWithLabelsSampleAsync(object):
3636
container_sas_url = os.environ["CONTAINER_SAS_URL"]
3737

3838
async def train_model_with_labels(self):
39-
# [START create_form_training_client_async]
4039
from azure.ai.formrecognizer.aio import FormTrainingClient
4140
from azure.core.credentials import AzureKeyCredential
4241

4342
form_training_client = FormTrainingClient(
4443
endpoint=self.endpoint, credential=AzureKeyCredential(self.key)
4544
)
46-
# [END create_form_training_client_async]
47-
async with form_training_client:
4845

46+
async with form_training_client:
4947
model = await form_training_client.train_model(self.container_sas_url, use_training_labels=True)
48+
5049
# Custom model information
5150
print("Model ID: {}".format(model.model_id))
5251
print("Status: {}".format(model.status))

sdk/formrecognizer/azure-ai-formrecognizer/samples/sample_get_bounding_boxes.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,11 @@ class GetBoundingBoxesSample(object):
3737

3838
def get_bounding_boxes(self):
3939
from azure.ai.formrecognizer import FormWord, FormLine
40-
# [START create_form_recognizer_client]
4140
from azure.core.credentials import AzureKeyCredential
4241
from azure.ai.formrecognizer import FormRecognizerClient
4342
form_recognizer_client = FormRecognizerClient(
4443
endpoint=self.endpoint, credential=AzureKeyCredential(self.key)
4544
)
46-
# [END create_form_recognizer_client]
4745

4846
# Make sure your form's type is included in the list of form types the custom model can recognize
4947
with open("sample_forms/forms/Form_1.jpg", "rb") as f:

sdk/formrecognizer/azure-ai-formrecognizer/samples/sample_train_model_with_labels.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,10 @@ class TrainModelWithLabelsSample(object):
3535
container_sas_url = os.environ["CONTAINER_SAS_URL"]
3636

3737
def train_model_with_labels(self):
38-
# [START create_form_training_client]
3938
from azure.ai.formrecognizer import FormTrainingClient
4039
from azure.core.credentials import AzureKeyCredential
4140

4241
form_training_client = FormTrainingClient(self.endpoint, AzureKeyCredential(self.key))
43-
# [END create_form_training_client]
4442

4543
poller = form_training_client.begin_train_model(self.container_sas_url, use_training_labels=True)
4644
model = poller.result()

sdk/formrecognizer/azure-ai-formrecognizer/tests/test_training.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import functools
88
from azure.core.credentials import AzureKeyCredential
9-
from azure.core.exceptions import ClientAuthenticationError
9+
from azure.core.exceptions import ClientAuthenticationError, HttpResponseError
1010
from azure.ai.formrecognizer._generated.models import Model
1111
from azure.ai.formrecognizer._models import CustomFormModel
1212
from azure.ai.formrecognizer import FormTrainingClient
@@ -209,6 +209,6 @@ def test_training_with_files_filter(self, client, container_sas_url):
209209
self.assertEqual(len(model.training_documents), 1)
210210
self.assertEqual(model.training_documents[0].document_name, "subfolder/Form_6.jpg") # we filtered for only subfolders
211211

212-
poller = client.begin_train_model(training_files_url=container_sas_url, prefix="xxx")
213-
model = poller.result()
214-
self.assertEqual(model.status, "invalid") # prefix doesn't include any files so training fails
212+
with self.assertRaises(HttpResponseError):
213+
poller = client.begin_train_model(training_files_url=container_sas_url, prefix="xxx")
214+
model = poller.result()

sdk/formrecognizer/azure-ai-formrecognizer/tests/test_training_async.py

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

77
import functools
88
from azure.core.credentials import AzureKeyCredential
9-
from azure.core.exceptions import ClientAuthenticationError
9+
from azure.core.exceptions import ClientAuthenticationError, HttpResponseError
1010
from azure.ai.formrecognizer._generated.models import Model
1111
from azure.ai.formrecognizer._models import CustomFormModel
1212
from azure.ai.formrecognizer.aio import FormTrainingClient
@@ -197,5 +197,5 @@ async def test_training_with_files_filter(self, client, container_sas_url):
197197
self.assertEqual(len(model.training_documents), 1)
198198
self.assertEqual(model.training_documents[0].document_name, "subfolder/Form_6.jpg") # we filtered for only subfolders
199199

200-
model = await client.train_model(training_files_url=container_sas_url, prefix="xxx")
201-
self.assertEqual(model.status, "invalid") # prefix doesn't include any files so training fails
200+
with self.assertRaises(HttpResponseError):
201+
model = await client.train_model(training_files_url=container_sas_url, prefix="xxx")

0 commit comments

Comments
 (0)