Skip to content

Commit ae7f628

Browse files
authored
Update client validation error messages to align with Heuristic evaluation criteria (Azure#27718)
* Reformat and update colors, raise MlException instead of generic
1 parent 5839fca commit ae7f628

File tree

13 files changed

+102
-69
lines changed

13 files changed

+102
-69
lines changed

sdk/ml/azure-ai-ml/azure/ai/ml/_artifacts/_blob_storage_helper.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
upload_file,
3333
)
3434
from azure.ai.ml.constants._common import STORAGE_AUTH_MISMATCH_ERROR
35-
from azure.ai.ml.exceptions import ErrorCategory, ErrorTarget, MlException, ValidationException
35+
from azure.ai.ml.exceptions import ErrorCategory, ErrorTarget, MLException, ValidationException
3636
from azure.core.exceptions import ResourceNotFoundError
3737
from azure.storage.blob import BlobServiceClient, ContainerClient
3838

@@ -229,7 +229,7 @@ def download(
229229
raise ex
230230
except Exception as e:
231231
msg = "Saving blob with prefix {} was unsuccessful. exception={}"
232-
raise MlException(
232+
raise MLException(
233233
message=msg.format(starts_with, e),
234234
no_personal_data_message=msg.format("[starts_with]", "[exception]"),
235235
target=ErrorTarget.ARTIFACT,

sdk/ml/azure-ai-ml/azure/ai/ml/_artifacts/_fileshare_storage_helper.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
get_directory_size,
2727
traverse_directory,
2828
)
29-
from azure.ai.ml.exceptions import ErrorCategory, ErrorTarget, MlException
29+
from azure.ai.ml.exceptions import ErrorCategory, ErrorTarget, MLException
3030
from azure.core.exceptions import ResourceExistsError, ResourceNotFoundError
3131
from azure.storage.fileshare import ShareDirectoryClient, ShareFileClient
3232

@@ -346,7 +346,7 @@ def recursive_download(
346346
recursive_download(sub_client, destination=destination, max_concurrency=max_concurrency)
347347
except Exception:
348348
msg = f"Saving fileshare directory with prefix {starts_with} was unsuccessful."
349-
raise MlException(
349+
raise MLException(
350350
message=msg.format(starts_with),
351351
no_personal_data_message=msg.format("[prefix]"),
352352
target=ErrorTarget.ARTIFACT,

sdk/ml/azure-ai-ml/azure/ai/ml/_artifacts/_gen2_storage_helper.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
upload_file,
2525
)
2626
from azure.ai.ml.constants._common import STORAGE_AUTH_MISMATCH_ERROR
27-
from azure.ai.ml.exceptions import ErrorCategory, ErrorTarget, MlException, ValidationException
27+
from azure.ai.ml.exceptions import ErrorCategory, ErrorTarget, MLException, ValidationException
2828
from azure.core.exceptions import ResourceExistsError
2929
from azure.storage.filedatalake import DataLakeServiceClient
3030

@@ -179,7 +179,7 @@ def download(self, starts_with: str, destination: str = Path.home()) -> None:
179179
raise ex
180180
except Exception as e:
181181
msg = "Saving output with prefix {} was unsuccessful. exception={}"
182-
raise MlException(
182+
raise MLException(
183183
message=msg.format(starts_with, e),
184184
no_personal_data_message=msg.format("[starts_with]", "[exception]"),
185185
target=ErrorTarget.ARTIFACT,

sdk/ml/azure-ai-ml/azure/ai/ml/_exception_helper.py

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import traceback
88
from typing import Dict, Tuple, Union
99

10-
from colorama import Fore, init
10+
from colorama import Fore, init, Style
1111
from marshmallow.exceptions import ValidationError as SchemaValidationError
1212

1313
from azure.ai.ml.constants._common import (
@@ -86,7 +86,7 @@ def format_details_section(
8686

8787
if hasattr(error, "message"):
8888
error_types[error.error_type] = True
89-
details += f"\n\n{error.message}\n"
89+
details += f"\n\n{Fore.RED}(x) {error.message}{Fore.RESET}\n"
9090
else:
9191
if (
9292
entity_type == ErrorTarget.COMMAND_JOB
@@ -139,12 +139,15 @@ def format_details_section(
139139
if isinstance(field_error, dict):
140140
field_error = f"{list(field_error.keys())[0]}:\n - {list(field_error.values())[0][0]}"
141141

142-
details += f"\n{field}:\n- {field_error}\n"
143-
142+
details += f"{Fore.RED}\n(x) {field}:\n- {field_error}{Fore.RESET}\n"
144143
return error_types, details
145144

146145

147-
def format_errors_and_resolutions_sections(entity_type: str, error_types: Dict[str, bool]) -> Tuple[str, str]:
146+
def format_errors_and_resolutions_sections(
147+
entity_type: str,
148+
error_types: Dict[str, bool],
149+
cli: bool
150+
) -> Tuple[str, str]:
148151
"""Builds strings for details of the error message template's Errors and Resolutions sections."""
149152

150153
resolutions = ""
@@ -153,40 +156,50 @@ def format_errors_and_resolutions_sections(entity_type: str, error_types: Dict[s
153156

154157
if error_types[ValidationErrorType.INVALID_VALUE]:
155158
errors += f"\n{count}) One or more fields are invalid"
156-
resolutions += f"Double-check that all specified parameters are of the correct types and formats \
157-
prescribed by the {entity_type} schema."
159+
resolutions += f"\n{count}) Double-check that all specified parameters are of the correct types and formats "\
160+
f"prescribed by the {entity_type} schema."
158161
count += 1
159162
if error_types[ValidationErrorType.UNKNOWN_FIELD]:
160163
errors += f"\n{count}) A least one unrecognized parameter is specified"
161-
resolutions += f"Remove any parameters not prescribed by the {entity_type} schema."
164+
resolutions += f"\n{count}) Remove any parameters not prescribed by the {entity_type} schema."
162165
count += 1
163166
if error_types[ValidationErrorType.MISSING_FIELD]:
164167
errors += f"\n{count}) At least one required parameter is missing"
165-
resolutions += f"Ensure all parameters required by the {entity_type} schema are specified."
168+
resolutions += f"\n{count}) Ensure all parameters required by the {entity_type} schema are specified."
166169
count += 1
167170
if error_types[ValidationErrorType.FILE_OR_FOLDER_NOT_FOUND]:
168171
errors += f"\n{count}) One or more files or folders do not exist.\n"
169-
resolutions += "Double-check the directory paths you provided and enter the correct paths."
172+
resolutions += f"\n{count}) Double-check the directory path you provided and enter the correct path."
170173
count += 1
171174
if error_types[ValidationErrorType.CANNOT_SERIALIZE]:
172175
errors += f"\n{count}) One or more fields cannot be serialized.\n"
173-
resolutions += f"Double-check that all specified parameters are of the correct types and formats \
176+
resolutions += f"\n{count}) Double-check that all specified parameters are of the correct types and formats \
174177
prescribed by the {entity_type} schema."
175178
count += 1
176179
if error_types[ValidationErrorType.CANNOT_PARSE]:
177180
errors += f"\n{count}) YAML file cannot be parsed.\n"
178-
resolutions += "Double-check your YAML file for syntax and formatting errors."
181+
resolutions += f"\n{count}) Double-check your YAML file for syntax and formatting errors."
179182
count += 1
180183
if error_types[ValidationErrorType.RESOURCE_NOT_FOUND]:
181184
errors += f"\n{count}) Resource was not found.\n"
182-
resolutions += "Double-check that the resource has been specified correctly and that you have access to it."
185+
resolutions += f"\n{count}) Double-check that the resource has been specified correctly and "\
186+
"that you have access to it."
183187
count += 1
184188

189+
if cli:
190+
errors = "Error: " + errors
191+
else:
192+
errors = Fore.BLACK + errors + Fore.RESET
193+
185194
return errors, resolutions
186195

187196

188197
def format_create_validation_error(
189-
error: Union[SchemaValidationError, ValidationException], yaml_operation: bool
198+
error: Union[SchemaValidationError,
199+
ValidationException],
200+
yaml_operation: bool,
201+
cli: bool = False,
202+
raw_error: str = None,
190203
) -> str:
191204
"""
192205
Formats a detailed error message for validation errors.
@@ -204,12 +217,15 @@ def format_create_validation_error(
204217
from azure.ai.ml._schema.assets.environment import EnvironmentSchema
205218
from azure.ai.ml._schema.assets.model import ModelSchema
206219

220+
if raw_error:
221+
error = raw_error
207222
entity_type, details = get_entity_type(error)
208223
error_types, details = format_details_section(error, details, entity_type)
209-
errors, resolutions = format_errors_and_resolutions_sections(entity_type, error_types)
224+
errors, resolutions = format_errors_and_resolutions_sections(entity_type, error_types, cli)
210225

211226
if yaml_operation:
212227
description = YAML_CREATION_ERROR_DESCRIPTION.format(entity_type=entity_type)
228+
description = Style.BRIGHT + description + Style.RESET_ALL
213229

214230
if entity_type == ErrorTarget.MODEL:
215231
schema_type = ModelSchema
@@ -242,9 +258,11 @@ def format_create_validation_error(
242258
error_msg=errors,
243259
parsed_error_details=details,
244260
resolutions=resolutions,
261+
text_color=Fore.WHITE,
262+
link_color=Fore.CYAN,
263+
reset=Fore.RESET
245264
)
246265

247-
formatted_error = Fore.RED + formatted_error + Fore.RESET
248266
return formatted_error
249267

250268

@@ -265,10 +283,16 @@ def log_and_raise_error(error, debug=False, yaml_operation=False):
265283
elif isinstance(error, ValidationException):
266284
module_logger.debug(traceback.format_exc())
267285
try:
268-
if error.error_type == ValidationErrorType.GENERIC:
286+
error_type = error.error_type
287+
if error_type == ValidationErrorType.GENERIC:
269288
formatted_error = error
270289
else:
271290
formatted_error = format_create_validation_error(error, yaml_operation=yaml_operation)
291+
raise ValidationException(
292+
message=formatted_error,
293+
no_personal_data_message="",
294+
error_type=error_type,
295+
)
272296
except NotImplementedError:
273297
formatted_error = error
274298
else:

sdk/ml/azure-ai-ml/azure/ai/ml/_local_endpoints/utilities/commandline_utility.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import sys
99
import time
1010

11-
from azure.ai.ml.exceptions import ErrorCategory, ErrorTarget, MlException
11+
from azure.ai.ml.exceptions import ErrorCategory, ErrorTarget, MLException
1212

1313

1414
def _print_command_results(test_passed, time_taken, output):
@@ -67,7 +67,7 @@ def run_cli_command(
6767
return json.loads(exclude_warnings(output))
6868
except Exception:
6969
msg = "Expected JSON, instead got: \n{}\n"
70-
raise MlException(
70+
raise MLException(
7171
message=msg.format(output),
7272
no_personal_data_message=msg.format("[something else]"),
7373
target=ErrorTarget.LOCAL_ENDPOINT,

sdk/ml/azure-ai-ml/azure/ai/ml/_telemetry/activity.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from azure.core.exceptions import HttpResponseError
2626

2727
from azure.ai.ml._utils.utils import _is_user_error_from_exception_type, _is_user_error_from_status_code, _str_to_bool
28-
from azure.ai.ml.exceptions import ErrorCategory, MlException
28+
from azure.ai.ml.exceptions import ErrorCategory, MLException
2929

3030
# Get environment variable IS_IN_CI_PIPELINE to decide whether it's in CI test
3131
IS_IN_CI_PIPELINE = _str_to_bool(os.environ.get("IS_IN_CI_PIPELINE", "False"))
@@ -122,7 +122,7 @@ def error_preprocess(activityLogger, exception):
122122
activityLogger.activity_info["errorCategory"] = error_category
123123
if exception.inner_exception:
124124
activityLogger.activity_info["innerException"] = type(exception.inner_exception).__name__
125-
elif isinstance(exception, MlException):
125+
elif isinstance(exception, MLException):
126126
# If exception is MLException, it will have error_category, message and target attributes and will log those
127127
# information in log_activity, no need more actions here.
128128
pass
@@ -192,7 +192,7 @@ def log_activity(
192192
# All the system and unknown errors except for NotImplementedError will be wrapped with a new exception.
193193
if IS_IN_CI_PIPELINE and not isinstance(e, NotImplementedError):
194194
if (
195-
isinstance(exception, MlException)
195+
isinstance(exception, MLException)
196196
and exception.error_category in [ErrorCategory.SYSTEM_ERROR, ErrorCategory.UNKNOWN] # pylint: disable=no-member
197197
) or (
198198
"errorCategory" in activityLogger.activity_info
@@ -214,7 +214,7 @@ def log_activity(
214214
if exception:
215215
message += ", Exception={}".format(type(exception).__name__)
216216
activityLogger.activity_info["exception"] = type(exception).__name__
217-
if isinstance(exception, MlException):
217+
if isinstance(exception, MLException):
218218
activityLogger.activity_info["errorMessage"] = exception.no_personal_data_message # pylint: disable=no-member
219219
activityLogger.activity_info["errorTarget"] = exception.target # pylint: disable=no-member
220220
activityLogger.activity_info["errorCategory"] = exception.error_category # pylint: disable=no-member

sdk/ml/azure-ai-ml/azure/ai/ml/_utils/_endpoint_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
ResourceNotFoundError,
2727
map_error,
2828
)
29-
from azure.ai.ml.exceptions import ErrorTarget, ValidationErrorType, ValidationException, MlException, ErrorCategory
29+
from azure.ai.ml.exceptions import ErrorTarget, ValidationErrorType, ValidationException, MLException, ErrorCategory
3030
from azure.core.polling import LROPoller
3131
from azure.core.rest import HttpResponse
3232
from azure.mgmt.core.exceptions import ARMErrorFormat
@@ -206,7 +206,7 @@ def validate_scoring_script(deployment):
206206
except Exception as err:
207207
if isinstance(err, ValidationException):
208208
raise err
209-
raise MlException(
209+
raise MLException(
210210
message= f"Failed to open scoring script {err.filename}.",
211211
no_personal_data_message= "Failed to open scoring script.",
212212
)

sdk/ml/azure-ai-ml/azure/ai/ml/constants/_common.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@
104104
EXPERIMENTAL_LINK_MESSAGE = (
105105
"and may change at any time. Please see https://aka.ms/azuremlexperimental for more information."
106106
)
107-
REF_DOC_YAML_SCHEMA_ERROR_MSG_FORMAT = "Visit this link to refer to the {} schema if needed: {}."
107+
REF_DOC_YAML_SCHEMA_ERROR_MSG_FORMAT = "\nVisit this link to refer to the {} schema if needed: {}."
108108
STORAGE_AUTH_MISMATCH_ERROR = "AuthorizationPermissionMismatch"
109109
SWEEP_JOB_BEST_CHILD_RUN_ID_PROPERTY_NAME = "best_child_run_id"
110110
BATCH_JOB_CHILD_RUN_NAME = "batchscoring"
@@ -139,20 +139,21 @@
139139
ANONYMOUS_COMPONENT_NAME = "azureml_anonymous"
140140
GIT_PATH_PREFIX = "git+"
141141
SCHEMA_VALIDATION_ERROR_TEMPLATE = (
142-
"\n\nError: {description}\n{error_msg}\n\n"
142+
"\n{text_color}{description}\n{error_msg}{reset}\n\n"
143143
"Details: {parsed_error_details}\n"
144-
"Resolutions:\n{resolutions}"
144+
"Resolutions: {resolutions}"
145145
"If using the CLI, you can also check the full log in debug mode for more details by adding --debug "
146146
"to the end of your command\n"
147-
"Additional Resources: The easiest way to author a yaml specification file is using IntelliSense and "
147+
"\nAdditional Resources: The easiest way to author a yaml specification file is using IntelliSense and "
148148
"auto-completion Azure ML VS code extension provides: "
149-
"https://code.visualstudio.com/docs/datascience/azure-machine-learning. "
150-
"To set up VS Code, visit https://docs.microsoft.com/azure/machine-learning/how-to-setup-vs-code\n"
149+
"{link_color}https://code.visualstudio.com/docs/datascience/azure-machine-learning.{reset} "
150+
"To set up VS Code, visit {link_color}https://docs.microsoft.com/azure/machine-learning/how-to-setup-vs-"
151+
"code{reset}\n"
151152
)
152153

153154
YAML_CREATION_ERROR_DESCRIPTION = (
154155
"The yaml file you provided does not match the prescribed schema "
155-
"for {entity_type} yaml files and/or has the following issues:"
156+
"for {entity_type} yaml files and/or has the following issues:\n"
156157
)
157158
DATASTORE_SCHEMA_TYPES = [
158159
"AzureFileSchema",

0 commit comments

Comments
 (0)