Skip to content

Commit

Permalink
Add schema links to error messages resolution section (#26646)
Browse files Browse the repository at this point in the history
* Add specific datastore types to ErrorTarget enum

* Add schema links to error messages resolution sections

* Add test

* Update datastore ErrorTarget enum

* Update to use proper key for yaml ref doc dictionary

* Fix pylint errors

* Update Resolution message

* Update Resolution message

* Update test

* Update test

* Fix broken symlink test
  • Loading branch information
diondrapeck authored Oct 11, 2022
1 parent 7379fa4 commit 5fe1008
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 13 deletions.
56 changes: 48 additions & 8 deletions sdk/ml/azure-ai-ml/azure/ai/ml/_exception_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,32 +154,32 @@ def format_errors_and_resolutions_sections(entity_type: str, error_types: Dict[s
if error_types[ValidationErrorType.INVALID_VALUE]:
errors += f"\n{count}) One or more fields are invalid"
resolutions += f"Double-check that all specified parameters are of the correct types and formats \
prescribed by the {entity_type} schema.\n"
prescribed by the {entity_type} schema."
count += 1
if error_types[ValidationErrorType.UNKNOWN_FIELD]:
errors += f"\n{count}) A least one unrecognized parameter is specified"
resolutions += f"Remove any parameters not prescribed by the {entity_type} schema.\n"
resolutions += f"Remove any parameters not prescribed by the {entity_type} schema."
count += 1
if error_types[ValidationErrorType.MISSING_FIELD]:
errors += f"\n{count}) At least one required parameter is missing"
resolutions += f"Ensure all parameters required by the {entity_type} schema are specified.\n"
resolutions += f"Ensure all parameters required by the {entity_type} schema are specified."
count += 1
if error_types[ValidationErrorType.FILE_OR_FOLDER_NOT_FOUND]:
errors += f"\n{count}) One or more files or folders do not exist.\n"
resolutions += "Double-check the directory paths you provided and enter the correct paths.\n"
resolutions += "Double-check the directory paths you provided and enter the correct paths."
count += 1
if error_types[ValidationErrorType.CANNOT_SERIALIZE]:
errors += f"\n{count}) One or more fields cannot be serialized.\n"
resolutions += f"Double-check that all specified parameters are of the correct types and formats \
prescribed by the {entity_type} schema.\n"
prescribed by the {entity_type} schema."
count += 1
if error_types[ValidationErrorType.CANNOT_PARSE]:
errors += f"\n{count}) YAML file cannot be parsed.\n"
resolutions += "Double-check your YAML file for syntax and formatting errors.\n"
resolutions += "Double-check your YAML file for syntax and formatting errors."
count += 1
if error_types[ValidationErrorType.RESOURCE_NOT_FOUND]:
errors += f"\n{count}) Resource was not found.\n"
resolutions += "Double-check that the resource has been specified correctly and that you have access to it.\n"
resolutions += "Double-check that the resource has been specified correctly and that you have access to it."
count += 1

return errors, resolutions
Expand All @@ -191,10 +191,50 @@ def format_create_validation_error(
"""
Formats a detailed error message for validation errors.
"""
from azure.ai.ml.entities._util import REF_DOC_ERROR_MESSAGE_MAP
from azure.ai.ml._schema.assets.data import DataSchema
from azure.ai.ml._schema._datastore import (
AzureBlobSchema,
AzureDataLakeGen1Schema,
AzureDataLakeGen2Schema,
AzureFileSchema,
)
from azure.ai.ml._schema.job import CommandJobSchema
from azure.ai.ml._schema._sweep import SweepJobSchema
from azure.ai.ml._schema.assets.environment import EnvironmentSchema
from azure.ai.ml._schema.assets.model import ModelSchema

entity_type, details = get_entity_type(error)
error_types, details = format_details_section(error, details, entity_type)
errors, resolutions = format_errors_and_resolutions_sections(entity_type, error_types)
description = YAML_CREATION_ERROR_DESCRIPTION.format(entity_type=entity_type) if yaml_operation else ""

if yaml_operation:
description = YAML_CREATION_ERROR_DESCRIPTION.format(entity_type=entity_type)

if entity_type == ErrorTarget.MODEL:
schema_type = ModelSchema
elif entity_type == ErrorTarget.DATA:
schema_type = DataSchema
elif entity_type == ErrorTarget.COMMAND_JOB:
schema_type = CommandJobSchema
elif entity_type == ErrorTarget.SWEEP_JOB:
schema_type = SweepJobSchema
elif entity_type in [ErrorTarget.BLOB_DATASTORE, ErrorTarget.DATASTORE]:
schema_type = AzureBlobSchema
elif entity_type == ErrorTarget.GEN1_DATASTORE:
schema_type = AzureDataLakeGen1Schema
elif entity_type == ErrorTarget.GEN2_DATASTORE:
schema_type = AzureDataLakeGen2Schema
elif entity_type == ErrorTarget.FILE_DATASTORE:
schema_type = AzureFileSchema
elif entity_type == ErrorTarget.ENVIRONMENT:
schema_type = EnvironmentSchema

resolutions += " " + REF_DOC_ERROR_MESSAGE_MAP.get(schema_type, "")
else:
description = ""

resolutions += "\n"
formatted_error = SCHEMA_VALIDATION_ERROR_TEMPLATE.format(
description=description,
error_msg=errors,
Expand Down
2 changes: 1 addition & 1 deletion sdk/ml/azure-ai-ml/azure/ai/ml/constants/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
EXPERIMENTAL_LINK_MESSAGE = (
"and may change at any time. Please see https://aka.ms/azuremlexperimental for more information."
)
REF_DOC_YAML_SCHEMA_ERROR_MSG_FORMAT = "For a more detailed breakdown of the {} schema, please see: {}."
REF_DOC_YAML_SCHEMA_ERROR_MSG_FORMAT = "Visit this link to refer to the {} schema if needed: {}."
STORAGE_AUTH_MISMATCH_ERROR = "AuthorizationPermissionMismatch"
SWEEP_JOB_BEST_CHILD_RUN_ID_PROPERTY_NAME = "best_child_run_id"
BATCH_JOB_CHILD_RUN_NAME = "batchscoring"
Expand Down
4 changes: 4 additions & 0 deletions sdk/ml/azure-ai-ml/azure/ai/ml/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ class ErrorTarget:
ONLINE_ENDPOINT = "OnlineEndpoint"
ASSET = "Asset"
DATASTORE = "Datastore"
BLOB_DATASTORE = "BlobDatastore"
FILE_DATASTORE = "FileDatastore"
GEN1_DATASTORE = "Gen1Datastore"
GEN2_DATASTORE = "Gen2Datastore"
WORKSPACE = "Workspace"
COMPUTE = "Compute"
DEPLOYMENT = "Deployment"
Expand Down
15 changes: 12 additions & 3 deletions sdk/ml/azure-ai-ml/tests/dataset/unittests/test_data_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
DatasetVersionDetails,
)
from azure.ai.ml._scope_dependent_operations import OperationConfig, OperationScope
from azure.ai.ml.constants._common import AssetTypes
from azure.ai.ml.constants._common import (
REF_DOC_YAML_SCHEMA_ERROR_MSG_FORMAT,
AssetTypes,
YAMLRefDocLinks,
YAMLRefDocSchemaNames,
)
from azure.ai.ml.entities._assets import Data
from azure.ai.ml.entities._assets._artifacts.artifact import ArtifactStorageInfo
from azure.ai.ml.exceptions import ErrorTarget
Expand Down Expand Up @@ -170,13 +175,17 @@ def test_create_or_update_missing_path(self, mock_data_operations: DataOperation
Expect to raise ValidationException for missing path
"""
name = "random_name"
data = Data(name=name, version="1", description="this is an mltable dataset", type=AssetTypes.MLTABLE)
data1 = Data(name=name, version="1", description="this is an mltable dataset", type=AssetTypes.MLTABLE)

with pytest.raises(Exception) as ex:
mock_data_operations.create_or_update(data)
mock_data_operations.create_or_update(data1)
assert "At least one required parameter is missing" in str(ex.value)
mock_data_operations._operation.create_or_update.assert_not_called()

with pytest.raises(Exception) as ex:
load_data("tests/test_configs/dataset/data_missing_path_test.yml")
assert REF_DOC_YAML_SCHEMA_ERROR_MSG_FORMAT.format(YAMLRefDocSchemaNames.DATA, YAMLRefDocLinks.DATA) in str(ex.value)

@patch("azure.ai.ml.operations._data_operations.read_local_mltable_metadata_contents")
@patch("azure.ai.ml.operations._data_operations.read_remote_mltable_metadata_contents")
@patch("azure.ai.ml.operations._data_operations.download_mltable_metadata_schema")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,11 @@ def link_file_path(
link_file_name = "link_file_rand_name.txt"
link_file = Path(os.path.join(os.path.abspath(storage_test_directory), link_file_name))

os.symlink(target_file_path, link_file)
try:
os.symlink(target_file_path, link_file)
except FileExistsError:
pass

assert os.path.islink(link_file)
link_file = convert_windows_path_to_unix(link_file)
yield link_file
Expand Down

0 comments on commit 5fe1008

Please sign in to comment.