diff --git a/sdk/formrecognizer/azure-ai-formrecognizer/CHANGELOG.md b/sdk/formrecognizer/azure-ai-formrecognizer/CHANGELOG.md index 7e891975ea53..f05f6ba48035 100644 --- a/sdk/formrecognizer/azure-ai-formrecognizer/CHANGELOG.md +++ b/sdk/formrecognizer/azure-ai-formrecognizer/CHANGELOG.md @@ -9,7 +9,7 @@ This version of the SDK defaults to the latest supported API version, which curr API version to analyze documents, with prebuilt and custom models. - Added new models to use with the new `DocumentAnalysisClient`: `AnalyzeResult`, `AnalyzedDocument`, `BoundingRegion`, `DocumentElement`, `DocumentEntity`, `DocumentField`, `DocumentKeyValuePair`, `DocumentKeyValueElement`, `DocumentLine`, `DocumentPage`, `DocumentSelectionMark`, `DocumentSpan`, `DocumentStyle`, `DocumentTable`, `DocumentTableCell`, `DocumentWord`. - Added new `DocumentModelAdministrationClient` with methods: `begin_build_model`, `begin_create_composed_model`, `begin_copy_model`, `get_copy_authorization`, `get_model`, `delete_model`, `list_models`, `get_operation`, `list_operations`, `get_account_info`, `get_document_analysis_client`. -- Added new models to use with the new `DocumentModelAdministrationClient`: `DocumentModel`, `DocumentModelInfo`, `DocTypeInfo`, `ModelOperation`, `ModelOperationInfo`, `AccountInfo`. +- Added new models to use with the new `DocumentModelAdministrationClient`: `DocumentModel`, `DocumentModelInfo`, `DocTypeInfo`, `ModelOperation`, `ModelOperationInfo`, `AccountInfo`, `DocumentAnalysisError`, `DocumentAnalysisInnerError`. - Added samples using the `DocumentAnalysisClient` and `DocumentModelAdministrationClient` under `/samples/v3.2-beta`. - Added `DocumentAnalysisApiVersion` to be used with `DocumentAnalysisClient` and `DocumentModelAdministrationClient`. diff --git a/sdk/formrecognizer/azure-ai-formrecognizer/azure/ai/formrecognizer/__init__.py b/sdk/formrecognizer/azure-ai-formrecognizer/azure/ai/formrecognizer/__init__.py index 63e10701ff75..c20ead661b69 100644 --- a/sdk/formrecognizer/azure-ai-formrecognizer/azure/ai/formrecognizer/__init__.py +++ b/sdk/formrecognizer/azure-ai-formrecognizer/azure/ai/formrecognizer/__init__.py @@ -58,7 +58,9 @@ DocumentModel, DocumentModelInfo, DocTypeInfo, - AccountInfo + AccountInfo, + DocumentAnalysisError, + DocumentAnalysisInnerError, ) from ._api_versions import FormRecognizerApiVersion, DocumentAnalysisApiVersion @@ -118,7 +120,9 @@ "DocumentModel", "DocumentModelInfo", "DocTypeInfo", - "AccountInfo" + "AccountInfo", + "DocumentAnalysisError", + "DocumentAnalysisInnerError", ] __VERSION__ = VERSION diff --git a/sdk/formrecognizer/azure-ai-formrecognizer/azure/ai/formrecognizer/_models.py b/sdk/formrecognizer/azure-ai-formrecognizer/azure/ai/formrecognizer/_models.py index 829d175af5cf..3f5fc6cfade1 100644 --- a/sdk/formrecognizer/azure-ai-formrecognizer/azure/ai/formrecognizer/_models.py +++ b/sdk/formrecognizer/azure-ai-formrecognizer/azure/ai/formrecognizer/_models.py @@ -1730,15 +1730,6 @@ def _from_generated(cls, err): else [] ) - @classmethod - def _from_generated_v3(cls, err): - if err.innererror: - return cls( - code=err.innererror.code, - message=err.innererror.message, - ) - return cls(code=err.code, message=err.message) - def __repr__(self): return "FormRecognizerError(code={}, message={})".format( self.code, self.message @@ -3326,9 +3317,9 @@ class ModelOperation(ModelOperationInfo): :vartype kind: str :ivar resource_location: URL of the resource targeted by this operation. :vartype resource_location: str - :ivar error: Encountered error, includes the error code and message for why + :ivar error: Encountered error, includes the error code, message, and details for why the operation failed. - :vartype error: ~azure.ai.formrecognizer.FormRecognizerError + :vartype error: ~azure.ai.formrecognizer.DocumentAnalysisError :ivar result: Operation result upon success. Returns a DocumentModel which contains all information about the model including the doc types and fields it can analyze from documents. @@ -3393,7 +3384,7 @@ def from_dict(cls, data): kind=data.get("kind", None), resource_location=data.get("resource_location", None), result=DocumentModel.from_dict(data.get("result")) if data.get("result") else None, # type: ignore - error=FormRecognizerError.from_dict(data.get("error")) if data.get("error") else None, # type: ignore + error=DocumentAnalysisError.from_dict(data.get("error")) if data.get("error") else None, # type: ignore ) @classmethod @@ -3409,7 +3400,7 @@ def _from_generated(cls, op, api_version): # pylint: disable=arguments-differ resource_location=op.resource_location, result=DocumentModel._from_generated(deserialize(ModelInfo, op.result)) if op.result else None, - error=FormRecognizerError._from_generated_v3(deserialize(Error, op.error)) + error=DocumentAnalysisError._from_generated(deserialize(Error, op.error)) if op.error else None ) @@ -3898,3 +3889,150 @@ def from_dict(cls, data): model_count=data.get("model_count", None), model_limit=data.get("model_limit", None), ) + + +class DocumentAnalysisError(object): + """DocumentAnalysisError contains the details of the error returned by the service. + + :ivar code: Error code. + :vartype code: str + :ivar message: Error message. + :vartype message: str + :ivar target: Target of the error. + :vartype target: str + :ivar details: List of detailed errors. + :vartype details: list[~azure.ai.formrecognizer.DocumentAnalysisError] + :ivar innererror: Detailed error. + :vartype innererror: ~azure.ai.formrecognizer.DocumentAnalysisInnerError + """ + + def __init__( + self, + **kwargs + ): + self.code = kwargs.get('code', None) + self.message = kwargs.get('message', None) + self.target = kwargs.get('target', None) + self.details = kwargs.get('details', None) + self.innererror = kwargs.get('innererror', None) + + def __repr__(self): + return ( + "DocumentAnalysisError(code={}, message={}, target={}, details={}, innererror={})".format( + self.code, + self.message, + self.target, + repr(self.details), + repr(self.innererror) + ) + ) + + @classmethod + def _from_generated(cls, err): + return cls( + code=err.code, + message=err.message, + target=err.target, + details=[DocumentAnalysisError._from_generated(e) for e in err.details] if err.details else [], + innererror=DocumentAnalysisInnerError._from_generated(err.innererror) if err.innererror else None + ) + + def to_dict(self): + # type: () -> dict + """Returns a dict representation of DocumentAnalysisError. + + :return: dict + :rtype: dict + """ + return { + "code": self.code, + "message": self.message, + "target": self.target, + "details": [detail.to_dict() for detail in self.details] if self.details else [], + "innererror": self.innererror.to_dict() if self.innererror else None + } + + @classmethod + def from_dict(cls, data): + # type: (dict) -> DocumentAnalysisError + """Converts a dict in the shape of a DocumentAnalysisError to the model itself. + + :param dict data: A dictionary in the shape of DocumentAnalysisError. + :return: DocumentAnalysisError + :rtype: DocumentAnalysisError + """ + return cls( + code=data.get("code", None), + message=data.get("message", None), + target=data.get("target", None), + details=[DocumentAnalysisError.from_dict(e) for e in data.get("details")] # type: ignore + if data.get("details") else [], + innererror=DocumentAnalysisInnerError.from_dict(data.get("innererror")) # type: ignore + if data.get("innererror") else None + ) + + +class DocumentAnalysisInnerError(object): + """Inner error details for the DocumentAnalysisError. + + :ivar code: Error code. + :vartype code: str + :ivar message: Error message. + :ivar innererror: Detailed error. + :vartype innererror: ~azure.ai.formrecognizer.DocumentAnalysisInnerError + """ + + def __init__( + self, + **kwargs + ): + + self.code = kwargs.get('code', None) + self.message = kwargs.get('message', None) + self.innererror = kwargs.get('innererror', None) + + def __repr__(self): + return ( + "DocumentAnalysisInnerError(code={}, message={}, innererror={})".format( + self.code, + self.message, + repr(self.innererror) + ) + ) + + @classmethod + def _from_generated(cls, ierr): + return cls( + code=ierr.code, + message=ierr.message, + innererror=DocumentAnalysisInnerError._from_generated(ierr.innererror) if ierr.innererror else None + ) + + def to_dict(self): + # type: () -> dict + """Returns a dict representation of DocumentAnalysisInnerError. + + :return: dict + :rtype: dict + """ + return { + "code": self.code, + "message": self.message, + "innererror": self.innererror.to_dict() if self.innererror else None + } + + @classmethod + def from_dict(cls, data): + # type: (dict) -> DocumentAnalysisInnerError + """Converts a dict in the shape of a DocumentAnalysisInnerError to the model itself. + + :param dict data: A dictionary in the shape of DocumentAnalysisInnerError. + :return: DocumentAnalysisInnerError + :rtype: DocumentAnalysisInnerError + """ + return cls( + code=data.get("code", None), + message=data.get("message", None), + innererror=DocumentAnalysisInnerError.from_dict(data.get("innererror")) # type: ignore + if data.get("innererror") else None + ) diff --git a/sdk/formrecognizer/azure-ai-formrecognizer/tests/test_mgmt.py b/sdk/formrecognizer/azure-ai-formrecognizer/tests/test_mgmt.py index 5d5c2e310e17..dbd32752e8f6 100644 --- a/sdk/formrecognizer/azure-ai-formrecognizer/tests/test_mgmt.py +++ b/sdk/formrecognizer/azure-ai-formrecognizer/tests/test_mgmt.py @@ -167,6 +167,7 @@ def test_get_list_operations(self, client): error = op.error assert error.code assert error.message + assert error.details @FormRecognizerPreparer() @DocumentModelAdministrationClientPreparer() diff --git a/sdk/formrecognizer/azure-ai-formrecognizer/tests/test_mgmt_async.py b/sdk/formrecognizer/azure-ai-formrecognizer/tests/test_mgmt_async.py index c86b73aba563..c56f1d12c1a9 100644 --- a/sdk/formrecognizer/azure-ai-formrecognizer/tests/test_mgmt_async.py +++ b/sdk/formrecognizer/azure-ai-formrecognizer/tests/test_mgmt_async.py @@ -178,6 +178,7 @@ async def test_get_list_operations(self, client): assert op.result is None assert error.code assert error.message + assert error.details @FormRecognizerPreparer() @DocumentModelAdministrationClientPreparer() diff --git a/sdk/formrecognizer/azure-ai-formrecognizer/tests/test_repr.py b/sdk/formrecognizer/azure-ai-formrecognizer/tests/test_repr.py index 90473c5461df..a2e8de9d9bbd 100644 --- a/sdk/formrecognizer/azure-ai-formrecognizer/tests/test_repr.py +++ b/sdk/formrecognizer/azure-ai-formrecognizer/tests/test_repr.py @@ -343,6 +343,55 @@ def document_model(doc_type_info): return model, model_repr +@pytest.fixture +def document_analysis_inner_error(): + model = _models.DocumentAnalysisInnerError( + code="ResourceNotFound", + message="Resource was not found", + innererror=_models.DocumentAnalysisInnerError( + code="ResourceNotFound", + message="Resource was not found", + ) + ) + model_repr = "DocumentAnalysisInnerError(code={}, message={}, innererror={})".format( + "ResourceNotFound", + "Resource was not found", + _models.DocumentAnalysisInnerError( + code="ResourceNotFound", + message="Resource was not found", + )) + assert repr(model) == model_repr + return model, model_repr + + +@pytest.fixture +def document_analysis_error(document_analysis_inner_error): + model = _models.DocumentAnalysisError( + code="ResourceNotFound", + message="Resource was not found", + target="resource", + details=[ + _models.DocumentAnalysisError( + code="ResourceNotFound", + message="Resource was not found" + ) + ], + innererror=document_analysis_inner_error[0] + ) + model_repr = "DocumentAnalysisError(code={}, message={}, target={}, details={}, innererror={})".format( + "ResourceNotFound", + "Resource was not found", + "resource", + [_models.DocumentAnalysisError( + code="ResourceNotFound", + message="Resource was not found" + )], + document_analysis_inner_error[1] + ) + assert repr(model) == model_repr + return model, model_repr + + class TestRepr(): # Not inheriting form FormRecognizerTest because that doesn't allow me to define pytest fixtures in the same file # Not worth moving pytest fixture definitions to conftest since all I would use is assertEqual and I can just use assert @@ -403,8 +452,8 @@ def test_analyze_result(self, document_page, document_table, document_key_value_ ) assert repr(model) == model_repr - def test_model_operation(self, form_recognizer_error, document_model): - model = _models.ModelOperation(operation_id="id", status="succeeded", percent_completed=99, created_on=datetime.datetime(2021, 9, 16, 10, 10, 59, 342380), last_updated_on=datetime.datetime(2021, 9, 16, 10, 10, 59, 342380), kind="documentModelCopyTo", resource_location="westus2", error=form_recognizer_error[0], result=document_model[0]) + def test_model_operation(self, document_analysis_error, document_model): + model = _models.ModelOperation(operation_id="id", status="succeeded", percent_completed=99, created_on=datetime.datetime(2021, 9, 16, 10, 10, 59, 342380), last_updated_on=datetime.datetime(2021, 9, 16, 10, 10, 59, 342380), kind="documentModelCopyTo", resource_location="westus2", error=document_analysis_error[0], result=document_model[0]) model_repr = "ModelOperation(operation_id={}, status={}, percent_completed={}, created_on={}, last_updated_on={}, kind={}, resource_location={}, result={}, error={})".format( "id", "succeeded", @@ -414,7 +463,7 @@ def test_model_operation(self, form_recognizer_error, document_model): "documentModelCopyTo", "westus2", document_model[1], - form_recognizer_error[1], + document_analysis_error[1], ) assert repr(model) == model_repr diff --git a/sdk/formrecognizer/azure-ai-formrecognizer/tests/test_to_dict_v3.py b/sdk/formrecognizer/azure-ai-formrecognizer/tests/test_to_dict_v3.py index 059a3537373a..e465585c706f 100644 --- a/sdk/formrecognizer/azure-ai-formrecognizer/tests/test_to_dict_v3.py +++ b/sdk/formrecognizer/azure-ai-formrecognizer/tests/test_to_dict_v3.py @@ -1548,8 +1548,23 @@ def test_model_operation_to_dict(self): ) }, ), - error=_models.FormRecognizerError( - code="NotFound", message="model not found" + error=_models.DocumentAnalysisError( + code="ResourceNotFound", + message="Resource was not found", + target="resource", + details=[ + _models.DocumentAnalysisError( + code="ResourceNotFound", message="Resource was not found" + ) + ], + innererror=_models.DocumentAnalysisInnerError( + code="ResourceNotFound", + message="Resource was not found", + innererror=_models.DocumentAnalysisInnerError( + code="ResourceNotFound", + message="Resource was not found", + ), + ), ), ) @@ -1620,7 +1635,29 @@ def test_model_operation_to_dict(self): } }, }, - "error": {"code": "NotFound", "message": "model not found"}, + "error": { + "code": "ResourceNotFound", + "message": "Resource was not found", + "target": "resource", + "details": [ + { + "code": "ResourceNotFound", + "message": "Resource was not found", + "target": None, + "details": [], + "innererror": None, + } + ], + "innererror": { + "code": "ResourceNotFound", + "message": "Resource was not found", + "innererror": { + "code": "ResourceNotFound", + "message": "Resource was not found", + "innererror": None, + }, + }, + }, } assert d == final @@ -1877,3 +1914,73 @@ def test_account_info_to_dict(self): final = {"model_limit": 5000, "model_count": 10} assert d == final + + def test_document_analysis_inner_error_to_dict(self): + model = _models.DocumentAnalysisInnerError( + code="ResourceNotFound", + message="Resource was not found", + innererror=_models.DocumentAnalysisInnerError( + code="ResourceNotFound", + message="Resource was not found", + ), + ) + + d = model.to_dict() + + final = { + "code": "ResourceNotFound", + "message": "Resource was not found", + "innererror": { + "code": "ResourceNotFound", + "message": "Resource was not found", + "innererror": None, + }, + } + assert d == final + + def test_document_analysis_error_to_dict(self): + model = _models.DocumentAnalysisError( + code="ResourceNotFound", + message="Resource was not found", + target="resource", + details=[ + _models.DocumentAnalysisError( + code="ResourceNotFound", message="Resource was not found" + ) + ], + innererror=_models.DocumentAnalysisInnerError( + code="ResourceNotFound", + message="Resource was not found", + innererror=_models.DocumentAnalysisInnerError( + code="ResourceNotFound", + message="Resource was not found", + ), + ), + ) + + d = model.to_dict() + + final = { + "code": "ResourceNotFound", + "message": "Resource was not found", + "target": "resource", + "details": [ + { + "code": "ResourceNotFound", + "message": "Resource was not found", + "target": None, + "details": [], + "innererror": None, + } + ], + "innererror": { + "code": "ResourceNotFound", + "message": "Resource was not found", + "innererror": { + "code": "ResourceNotFound", + "message": "Resource was not found", + "innererror": None, + }, + }, + } + assert d == final