From 555a6c2fd0b395add729389008ef49b952b8e979 Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Mon, 21 Mar 2022 23:16:46 -0700 Subject: [PATCH 01/23] Make the schema validation error for non-existing params more informative --- altair/utils/schemapi.py | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/altair/utils/schemapi.py b/altair/utils/schemapi.py index b94017c94..f2aa4409e 100644 --- a/altair/utils/schemapi.py +++ b/altair/utils/schemapi.py @@ -15,6 +15,8 @@ from altair import vegalite + +JSONSCHEMA_VALIDATOR = jsonschema.Draft7Validator # If DEBUG_MODE is True, then schema objects are converted to dict and # validated at creation time. This slows things down, particularly for # larger specs, but leads to much more useful tracebacks for the user. @@ -145,14 +147,39 @@ def __str__(self): for val in schema_path[:-1] if val not in ("properties", "additionalProperties", "patternProperties") ) - return """Invalid specification + if hasattr(vegalite, schema_path.split(".")[-1]): + vegalite_core_class = getattr(vegalite, schema_path.split(".")[-1]) + param_dict_keys = inspect.signature(vegalite_core_class).parameters.keys() + param_names = ( + "Existing parameter names are: " + + str([name for name in param_dict_keys if name != "kwds"])[1:-1] + ) + altair_class = "altair." + schema_path.split(".")[-1] + return """Invalid specification - {}, validating {!r} + {}, validating {!r} - {} - """.format( - schema_path, self.validator, self.message - ) + {} has no parameter named {!r} + + {} + + See the help for {} to read the full description of these parameters + """.format( + schema_path, + self.validator, + altair_class, + list(self.instance.keys())[0], + textwrap.fill(param_names, subsequent_indent=" " * 12, width=80), + altair_class, + ) + # Fall back on the less informative error message + else: + return """Invalid specification + {}, validating {!r} + {} + """.format( + schema_path, self.validator, self.message + ) class UndefinedType(object): From 430b17b7bc7f374451c86c99dc396217161cf79b Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Tue, 22 Mar 2022 13:08:45 -0700 Subject: [PATCH 02/23] Neatly format the existing params as a table --- altair/utils/schemapi.py | 95 +++++++++++++++++++++++++++++++--------- 1 file changed, 75 insertions(+), 20 deletions(-) diff --git a/altair/utils/schemapi.py b/altair/utils/schemapi.py index f2aa4409e..db5def698 100644 --- a/altair/utils/schemapi.py +++ b/altair/utils/schemapi.py @@ -6,6 +6,7 @@ import json import textwrap from typing import Any +from itertools import zip_longest import jsonschema import jsonschema.exceptions @@ -147,30 +148,84 @@ def __str__(self): for val in schema_path[:-1] if val not in ("properties", "additionalProperties", "patternProperties") ) + if hasattr(vegalite, schema_path.split(".")[-1]): + altair_class = "altair." + schema_path.split(".")[-1] vegalite_core_class = getattr(vegalite, schema_path.split(".")[-1]) param_dict_keys = inspect.signature(vegalite_core_class).parameters.keys() - param_names = ( - "Existing parameter names are: " - + str([name for name in param_dict_keys if name != "kwds"])[1:-1] - ) - altair_class = "altair." + schema_path.split(".")[-1] - return """Invalid specification - - {}, validating {!r} - - {} has no parameter named {!r} - {} - - See the help for {} to read the full description of these parameters - """.format( - schema_path, - self.validator, - altair_class, - list(self.instance.keys())[0], - textwrap.fill(param_names, subsequent_indent=" " * 12, width=80), - altair_class, + # Everything from here until the return statement is to format + # param names into a table so that they are easier to read + param_names, name_lengths = zip( + *[(name, len(name)) for name in param_dict_keys if name != "kwds"] + ) + # Worst case scenario with the same longest param name in the same + # row for all columns + max_name_length = max(name_lengths) + max_column_width = 80 + # Output a square table if not too big (since it is easier to read) + num_param_names = len(param_names) + square_columns = int(np.ceil(num_param_names**0.5)) + columns = min(max_column_width // max_name_length, square_columns) + + # Compute roughly equal column heights to evenly divide the param names + def split_into_equal_parts(n, p): + return [n // p + 1] * (n % p) + [n // p] * (p - n % p) + + column_heights = split_into_equal_parts(num_param_names, columns) + + # Section the param names into columns and compute their widths + param_names_columns = [] + column_max_widths = [] + last_end_idx = 0 + for ch in column_heights: + param_names_columns.append( + param_names[last_end_idx : last_end_idx + ch] + ) + column_max_widths.append( + max([len(param_name) for param_name in param_names_columns[-1]]) + ) + last_end_idx = ch + last_end_idx + + # Transpose the param name columns into rows to facilitate looping + param_names_rows = [] + for li in zip_longest(*param_names_columns, fillvalue=""): + param_names_rows.append(li) + # Build the table as a string by iterating over and formatting the rows + param_names_table = "" + for param_names_row in param_names_rows: + for num, param_name in enumerate(param_names_row): + # Set column width based on the longest param in the column + max_name_length_column = column_max_widths[num] + column_pad = 3 + param_names_table += "{:<{}}".format( + param_name, max_name_length_column + column_pad + ) + # Insert newlines and spacing after the last element in each row + if num == (len(param_names_row) - 1): + param_names_table += "\n" + # 16 is the indendation of the returned multiline string below + param_names_table += " " * 16 + + # cleandoc removes multiline string indentation in the output + return inspect.cleandoc( + """Invalid specification + + {}, validating {!r} + + {} has no parameter named {!r} + + Existing parameter names are: + {} + See the help for {} to read the full description of these parameters + """.format( + schema_path, + self.validator, + altair_class, + list(self.instance.keys())[0], + param_names_table, + altair_class, + ) ) # Fall back on the less informative error message else: From 45f11b6ee04a178381d707f8a3983e28cc7eecde Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Mon, 18 Apr 2022 19:39:59 -0700 Subject: [PATCH 03/23] Use more reliable way of returning the non-existing param name My original approach sometimes returned the name of an existing parameter. This commit uses the same approach as the fallback but extracts just the parameter name from the message string. --- altair/utils/schemapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/altair/utils/schemapi.py b/altair/utils/schemapi.py index db5def698..bc98717dc 100644 --- a/altair/utils/schemapi.py +++ b/altair/utils/schemapi.py @@ -222,7 +222,7 @@ def split_into_equal_parts(n, p): schema_path, self.validator, altair_class, - list(self.instance.keys())[0], + self.message.split("('")[-1].split("'")[0], param_names_table, altair_class, ) From 95b1c60908c9429d75f34cefc2c0e8b204db1223 Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Fri, 6 Jan 2023 13:17:40 +0100 Subject: [PATCH 04/23] Break out table formatting into its own function --- altair/utils/schemapi.py | 107 ++++++++++++++++++++------------------- 1 file changed, 54 insertions(+), 53 deletions(-) diff --git a/altair/utils/schemapi.py b/altair/utils/schemapi.py index bc98717dc..a3043871e 100644 --- a/altair/utils/schemapi.py +++ b/altair/utils/schemapi.py @@ -139,6 +139,59 @@ def _get_contents(err): contents = {key: getattr(err, key) for key in spec.args[1:]} return contents + @staticmethod + def _format_params_as_table(param_dict_keys): + """Format param names into a table so that they are easier to read""" + param_names, name_lengths = zip( + *[(name, len(name)) for name in param_dict_keys if name != "kwds"] + ) + # Worst case scenario with the same longest param name in the same + # row for all columns + max_name_length = max(name_lengths) + max_column_width = 80 + # Output a square table if not too big (since it is easier to read) + num_param_names = len(param_names) + square_columns = int(np.ceil(num_param_names**0.5)) + columns = min(max_column_width // max_name_length, square_columns) + + # Compute roughly equal column heights to evenly divide the param names + def split_into_equal_parts(n, p): + return [n // p + 1] * (n % p) + [n // p] * (p - n % p) + + column_heights = split_into_equal_parts(num_param_names, columns) + + # Section the param names into columns and compute their widths + param_names_columns = [] + column_max_widths = [] + last_end_idx = 0 + for ch in column_heights: + param_names_columns.append(param_names[last_end_idx : last_end_idx + ch]) + column_max_widths.append( + max([len(param_name) for param_name in param_names_columns[-1]]) + ) + last_end_idx = ch + last_end_idx + + # Transpose the param name columns into rows to facilitate looping + param_names_rows = [] + for li in zip_longest(*param_names_columns, fillvalue=""): + param_names_rows.append(li) + # Build the table as a string by iterating over and formatting the rows + param_names_table = "" + for param_names_row in param_names_rows: + for num, param_name in enumerate(param_names_row): + # Set column width based on the longest param in the column + max_name_length_column = column_max_widths[num] + column_pad = 3 + param_names_table += "{:<{}}".format( + param_name, max_name_length_column + column_pad + ) + # Insert newlines and spacing after the last element in each row + if num == (len(param_names_row) - 1): + param_names_table += "\n" + # 16 is the indendation of the returned multiline string below + param_names_table += " " * 16 + return param_names_table + def __str__(self): cls = self.obj.__class__ schema_path = ["{}.{}".format(cls.__module__, cls.__name__)] @@ -153,59 +206,7 @@ def __str__(self): altair_class = "altair." + schema_path.split(".")[-1] vegalite_core_class = getattr(vegalite, schema_path.split(".")[-1]) param_dict_keys = inspect.signature(vegalite_core_class).parameters.keys() - - # Everything from here until the return statement is to format - # param names into a table so that they are easier to read - param_names, name_lengths = zip( - *[(name, len(name)) for name in param_dict_keys if name != "kwds"] - ) - # Worst case scenario with the same longest param name in the same - # row for all columns - max_name_length = max(name_lengths) - max_column_width = 80 - # Output a square table if not too big (since it is easier to read) - num_param_names = len(param_names) - square_columns = int(np.ceil(num_param_names**0.5)) - columns = min(max_column_width // max_name_length, square_columns) - - # Compute roughly equal column heights to evenly divide the param names - def split_into_equal_parts(n, p): - return [n // p + 1] * (n % p) + [n // p] * (p - n % p) - - column_heights = split_into_equal_parts(num_param_names, columns) - - # Section the param names into columns and compute their widths - param_names_columns = [] - column_max_widths = [] - last_end_idx = 0 - for ch in column_heights: - param_names_columns.append( - param_names[last_end_idx : last_end_idx + ch] - ) - column_max_widths.append( - max([len(param_name) for param_name in param_names_columns[-1]]) - ) - last_end_idx = ch + last_end_idx - - # Transpose the param name columns into rows to facilitate looping - param_names_rows = [] - for li in zip_longest(*param_names_columns, fillvalue=""): - param_names_rows.append(li) - # Build the table as a string by iterating over and formatting the rows - param_names_table = "" - for param_names_row in param_names_rows: - for num, param_name in enumerate(param_names_row): - # Set column width based on the longest param in the column - max_name_length_column = column_max_widths[num] - column_pad = 3 - param_names_table += "{:<{}}".format( - param_name, max_name_length_column + column_pad - ) - # Insert newlines and spacing after the last element in each row - if num == (len(param_names_row) - 1): - param_names_table += "\n" - # 16 is the indendation of the returned multiline string below - param_names_table += " " * 16 + param_names_table = self._format_params_as_table(param_dict_keys) # cleandoc removes multiline string indentation in the output return inspect.cleandoc( From 9bd738b3d2f3b70ca1c0ebe3b2dd377079b2aa64 Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Sun, 8 Jan 2023 15:08:52 +0100 Subject: [PATCH 05/23] Add changes into code generation file --- tools/schemapi/schemapi.py | 97 +++++++++++++++++++++++++++++++++++--- 1 file changed, 90 insertions(+), 7 deletions(-) diff --git a/tools/schemapi/schemapi.py b/tools/schemapi/schemapi.py index 73e530fc8..dbe85bf10 100644 --- a/tools/schemapi/schemapi.py +++ b/tools/schemapi/schemapi.py @@ -4,6 +4,7 @@ import json import textwrap from typing import Any +from itertools import zip_longest import jsonschema import jsonschema.exceptions @@ -13,6 +14,8 @@ from altair import vegalite + +JSONSCHEMA_VALIDATOR = jsonschema.Draft7Validator # If DEBUG_MODE is True, then schema objects are converted to dict and # validated at creation time. This slows things down, particularly for # larger specs, but leads to much more useful tracebacks for the user. @@ -134,6 +137,59 @@ def _get_contents(err): contents = {key: getattr(err, key) for key in spec.args[1:]} return contents + @staticmethod + def _format_params_as_table(param_dict_keys): + """Format param names into a table so that they are easier to read""" + param_names, name_lengths = zip( + *[(name, len(name)) for name in param_dict_keys if name != "kwds"] + ) + # Worst case scenario with the same longest param name in the same + # row for all columns + max_name_length = max(name_lengths) + max_column_width = 80 + # Output a square table if not too big (since it is easier to read) + num_param_names = len(param_names) + square_columns = int(np.ceil(num_param_names**0.5)) + columns = min(max_column_width // max_name_length, square_columns) + + # Compute roughly equal column heights to evenly divide the param names + def split_into_equal_parts(n, p): + return [n // p + 1] * (n % p) + [n // p] * (p - n % p) + + column_heights = split_into_equal_parts(num_param_names, columns) + + # Section the param names into columns and compute their widths + param_names_columns = [] + column_max_widths = [] + last_end_idx = 0 + for ch in column_heights: + param_names_columns.append(param_names[last_end_idx : last_end_idx + ch]) + column_max_widths.append( + max([len(param_name) for param_name in param_names_columns[-1]]) + ) + last_end_idx = ch + last_end_idx + + # Transpose the param name columns into rows to facilitate looping + param_names_rows = [] + for li in zip_longest(*param_names_columns, fillvalue=""): + param_names_rows.append(li) + # Build the table as a string by iterating over and formatting the rows + param_names_table = "" + for param_names_row in param_names_rows: + for num, param_name in enumerate(param_names_row): + # Set column width based on the longest param in the column + max_name_length_column = column_max_widths[num] + column_pad = 3 + param_names_table += "{:<{}}".format( + param_name, max_name_length_column + column_pad + ) + # Insert newlines and spacing after the last element in each row + if num == (len(param_names_row) - 1): + param_names_table += "\n" + # 16 is the indendation of the returned multiline string below + param_names_table += " " * 16 + return param_names_table + def __str__(self): cls = self.obj.__class__ schema_path = ["{}.{}".format(cls.__module__, cls.__name__)] @@ -143,14 +199,41 @@ def __str__(self): for val in schema_path[:-1] if val not in ("properties", "additionalProperties", "patternProperties") ) - return """Invalid specification - {}, validating {!r} - - {} - """.format( - schema_path, self.validator, self.message - ) + if hasattr(vegalite, schema_path.split(".")[-1]): + altair_class = "altair." + schema_path.split(".")[-1] + vegalite_core_class = getattr(vegalite, schema_path.split(".")[-1]) + param_dict_keys = inspect.signature(vegalite_core_class).parameters.keys() + param_names_table = self._format_params_as_table(param_dict_keys) + + # cleandoc removes multiline string indentation in the output + return inspect.cleandoc( + """Invalid specification + + {}, validating {!r} + + {} has no parameter named {!r} + + Existing parameter names are: + {} + See the help for {} to read the full description of these parameters + """.format( + schema_path, + self.validator, + altair_class, + self.message.split("('")[-1].split("'")[0], + param_names_table, + altair_class, + ) + ) + # Fall back on the less informative error message + else: + return """Invalid specification + {}, validating {!r} + {} + """.format( + schema_path, self.validator, self.message + ) class UndefinedType(object): From 4eeac6397a5d9dc78e7653ad476727ad01c16ab6 Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Sun, 8 Jan 2023 15:14:07 +0100 Subject: [PATCH 06/23] Remove mistakingly added old lines --- altair/utils/schemapi.py | 1 - tools/schemapi/schemapi.py | 1 - 2 files changed, 2 deletions(-) diff --git a/altair/utils/schemapi.py b/altair/utils/schemapi.py index a3043871e..5de26e9bd 100644 --- a/altair/utils/schemapi.py +++ b/altair/utils/schemapi.py @@ -17,7 +17,6 @@ from altair import vegalite -JSONSCHEMA_VALIDATOR = jsonschema.Draft7Validator # If DEBUG_MODE is True, then schema objects are converted to dict and # validated at creation time. This slows things down, particularly for # larger specs, but leads to much more useful tracebacks for the user. diff --git a/tools/schemapi/schemapi.py b/tools/schemapi/schemapi.py index dbe85bf10..03d19bcfa 100644 --- a/tools/schemapi/schemapi.py +++ b/tools/schemapi/schemapi.py @@ -15,7 +15,6 @@ from altair import vegalite -JSONSCHEMA_VALIDATOR = jsonschema.Draft7Validator # If DEBUG_MODE is True, then schema objects are converted to dict and # validated at creation time. This slows things down, particularly for # larger specs, but leads to much more useful tracebacks for the user. From f994245d49540714a8e277c0def9b5e57b5a60b9 Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Sat, 18 Feb 2023 10:21:19 -0800 Subject: [PATCH 07/23] Only show existing parameters if an unknown parameter was used --- altair/utils/schemapi.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/altair/utils/schemapi.py b/altair/utils/schemapi.py index 749ea58b2..31dd42e88 100644 --- a/altair/utils/schemapi.py +++ b/altair/utils/schemapi.py @@ -242,7 +242,8 @@ def __str__(self): [e.message for e in self._additional_errors] ) - if hasattr(vegalite, schema_path.split(".")[-1]): + # Output all existing parameters when an unknown parameter is specified + if hasattr(vegalite, schema_path.split(".")[-1]) and self.validator == 'additionalProperties': altair_class = "altair." + schema_path.split(".")[-1] vegalite_core_class = getattr(vegalite, schema_path.split(".")[-1]) param_dict_keys = inspect.signature(vegalite_core_class).parameters.keys() @@ -268,7 +269,7 @@ def __str__(self): altair_class, ) ) - # Fall back on the less informative error message + # Use the default error message for all other cases than unknown parameter errors else: return """Invalid specification {}, validating {!r} From bee7b8bc389f9c01b580f5b8412ea26e1b7750e9 Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Sat, 18 Feb 2023 10:21:48 -0800 Subject: [PATCH 08/23] Update tests to check for detailed parameter errors --- tests/utils/tests/test_schemapi.py | 32 +++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/tests/utils/tests/test_schemapi.py b/tests/utils/tests/test_schemapi.py index af48ea74d..cc50d6ee8 100644 --- a/tests/utils/tests/test_schemapi.py +++ b/tests/utils/tests/test_schemapi.py @@ -467,8 +467,19 @@ def chart_example_invalid_y_option_value_with_condition(): [ ( chart_example_invalid_y_option, - r"schema.channels.X.*" - + r"Additional properties are not allowed \('unknown' was unexpected\)", +r"""Invalid specification + +altair.vegalite.v.?.schema.channels.X, validating 'additionalProperties' + +altair.X has no parameter named 'unknown' + +Existing parameter names are: +shorthand bin scale timeUnit +aggregate field sort title +axis impute stack type +bandPosition + +See the help for altair.X to read the full description of these parameters""" ), ( chart_example_invalid_y_option_value, @@ -478,8 +489,19 @@ def chart_example_invalid_y_option_value_with_condition(): ), ( chart_example_layer, - r"api.VConcatChart.*" - + r"Additional properties are not allowed \('width' was unexpected\)", +r"""Invalid specification + +altair.vegalite.v.?.api.VConcatChart, validating 'additionalProperties' + +altair.VConcatChart has no parameter named 'width' + +Existing parameter names are: +self bounds datasets params title +vconcat center description resolve transform +autosize config name spacing usermeta +background data padding + +See the help for altair.VConcatChart to read the full description of these parameters""" ), ( chart_example_invalid_y_option_value_with_condition, @@ -496,7 +518,7 @@ def chart_example_invalid_y_option_value_with_condition(): ( chart_example_invalid_channel_and_condition, r"schema.core.Encoding->encoding.*" - + r"Additional properties are not allowed \('invalidChannel' was unexpected\)", + + r"Additional properties are not allowed \('invalidChannel' was unexpected\).*", ), ], ) From 4395f58b9f30e903346d23dc0bd88c6c2ff65202 Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Sat, 18 Feb 2023 10:22:37 -0800 Subject: [PATCH 09/23] Trim error messages to be more to the point by removing unhelpful info --- altair/utils/schemapi.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/altair/utils/schemapi.py b/altair/utils/schemapi.py index 31dd42e88..52715b91c 100644 --- a/altair/utils/schemapi.py +++ b/altair/utils/schemapi.py @@ -244,25 +244,19 @@ def __str__(self): # Output all existing parameters when an unknown parameter is specified if hasattr(vegalite, schema_path.split(".")[-1]) and self.validator == 'additionalProperties': - altair_class = "altair." + schema_path.split(".")[-1] + altair_class = schema_path.split(".")[-1] vegalite_core_class = getattr(vegalite, schema_path.split(".")[-1]) param_dict_keys = inspect.signature(vegalite_core_class).parameters.keys() param_names_table = self._format_params_as_table(param_dict_keys) - # cleandoc removes multiline string indentation in the output + # `cleandoc` removes multiline string indentation in the output return inspect.cleandoc( - """Invalid specification - - {}, validating {!r} - - {} has no parameter named {!r} + """`{}` has no parameter named {!r} Existing parameter names are: {} - See the help for {} to read the full description of these parameters + See the help for `{}` to read the full description of these parameters """.format( - schema_path, - self.validator, altair_class, message.split("('")[-1].split("'")[0], param_names_table, @@ -271,11 +265,11 @@ def __str__(self): ) # Use the default error message for all other cases than unknown parameter errors else: - return """Invalid specification - {}, validating {!r} - {} - """.format( - schema_path, self.validator, message + return inspect.cleandoc( + """{} + """.format( + message + ) ) From 8848228b54ae680709f0691c343274816ccffce4 Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Sat, 18 Feb 2023 10:23:14 -0800 Subject: [PATCH 10/23] Add a general heading to error messages with multiple errors so that they work nicely with the cmopressed error message format --- altair/utils/schemapi.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/altair/utils/schemapi.py b/altair/utils/schemapi.py index 52715b91c..a797b095f 100644 --- a/altair/utils/schemapi.py +++ b/altair/utils/schemapi.py @@ -238,9 +238,12 @@ def __str__(self): ) message = self.message if self._additional_errors: - message += "\n " + "\n ".join( - [e.message for e in self._additional_errors] - ) + # The indentation here must match that of `cleandoc` below + additional_errors = "\n ".join([e.message for e in self._additional_errors]) + message = f"""'{self.instance}' is an invalid value for `{self.absolute_path[-1]}`: + + {message} + {additional_errors}""" # Output all existing parameters when an unknown parameter is specified if hasattr(vegalite, schema_path.split(".")[-1]) and self.validator == 'additionalProperties': From 1ce2bcfb7af54bee4c4b8c8fcf51890555426b2f Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Sat, 18 Feb 2023 11:11:11 -0800 Subject: [PATCH 11/23] Remove "self" from listed params --- altair/utils/schemapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/altair/utils/schemapi.py b/altair/utils/schemapi.py index a797b095f..ececd26bc 100644 --- a/altair/utils/schemapi.py +++ b/altair/utils/schemapi.py @@ -164,7 +164,7 @@ def __init__(self, obj, err): def _format_params_as_table(param_dict_keys): """Format param names into a table so that they are easier to read""" param_names, name_lengths = zip( - *[(name, len(name)) for name in param_dict_keys if name != "kwds"] + *[(name, len(name)) for name in param_dict_keys if name not in ["kwds", "self"]] ) # Worst case scenario with the same longest param name in the same # row for all columns From 31948e8d95b758dd0fbdd6ba0432ccfdc047dc5f Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Sat, 18 Feb 2023 11:11:38 -0800 Subject: [PATCH 12/23] Blake format and use cleandoc for proper indendation in the source --- altair/utils/schemapi.py | 9 ++++- tests/utils/tests/test_schemapi.py | 65 ++++++++++++++++-------------- 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/altair/utils/schemapi.py b/altair/utils/schemapi.py index ececd26bc..e344ce73d 100644 --- a/altair/utils/schemapi.py +++ b/altair/utils/schemapi.py @@ -239,14 +239,19 @@ def __str__(self): message = self.message if self._additional_errors: # The indentation here must match that of `cleandoc` below - additional_errors = "\n ".join([e.message for e in self._additional_errors]) + additional_errors = "\n ".join( + [e.message for e in self._additional_errors] + ) message = f"""'{self.instance}' is an invalid value for `{self.absolute_path[-1]}`: {message} {additional_errors}""" # Output all existing parameters when an unknown parameter is specified - if hasattr(vegalite, schema_path.split(".")[-1]) and self.validator == 'additionalProperties': + if ( + hasattr(vegalite, schema_path.split(".")[-1]) + and self.validator == "additionalProperties" + ): altair_class = schema_path.split(".")[-1] vegalite_core_class = getattr(vegalite, schema_path.split(".")[-1]) param_dict_keys = inspect.signature(vegalite_core_class).parameters.keys() diff --git a/tests/utils/tests/test_schemapi.py b/tests/utils/tests/test_schemapi.py index cc50d6ee8..3dc9028da 100644 --- a/tests/utils/tests/test_schemapi.py +++ b/tests/utils/tests/test_schemapi.py @@ -2,6 +2,7 @@ # tools/generate_schema_wrapper.py. Do not modify directly. import copy import io +import inspect import json import jsonschema import re @@ -467,58 +468,60 @@ def chart_example_invalid_y_option_value_with_condition(): [ ( chart_example_invalid_y_option, -r"""Invalid specification + inspect.cleandoc( + r"""`X` has no parameter named 'unknown' -altair.vegalite.v.?.schema.channels.X, validating 'additionalProperties' + Existing parameter names are: + shorthand bin scale timeUnit + aggregate field sort title + axis impute stack type + bandPosition -altair.X has no parameter named 'unknown' - -Existing parameter names are: -shorthand bin scale timeUnit -aggregate field sort title -axis impute stack type -bandPosition - -See the help for altair.X to read the full description of these parameters""" + See the help for `X` to read the full description of these parameters""" + ), ), ( chart_example_invalid_y_option_value, - r"schema.channels.Y.*" + r"'asdf' is an invalid value for `stack`.*" + r"'asdf' is not one of \['zero', 'center', 'normalize'\].*" + r"'asdf' is not of type 'null'.*'asdf' is not of type 'boolean'", ), ( chart_example_layer, -r"""Invalid specification - -altair.vegalite.v.?.api.VConcatChart, validating 'additionalProperties' - -altair.VConcatChart has no parameter named 'width' + inspect.cleandoc( + r"""`VConcatChart` has no parameter named 'width' -Existing parameter names are: -self bounds datasets params title -vconcat center description resolve transform -autosize config name spacing usermeta -background data padding + Existing parameter names are: + self bounds datasets params title + vconcat center description resolve transform + autosize config name spacing usermeta + background data padding -See the help for altair.VConcatChart to read the full description of these parameters""" + See the help for `VConcatChart` to read the full description of these parameters""" + ), ), ( chart_example_invalid_y_option_value_with_condition, - r"schema.channels.Y.*" - + r"'asdf' is not one of \['zero', 'center', 'normalize'\].*" - + r"'asdf' is not of type 'null'.*'asdf' is not of type 'boolean'", + inspect.cleandoc( + r"""'asdf' is an invalid value for `stack`: + + 'asdf' is not one of \['zero', 'center', 'normalize'\] + 'asdf' is not of type 'null' + 'asdf' is not of type 'boolean'""" + ), ), ( chart_example_hconcat, - r"schema.core.TitleParams.*" - + r"\{'text': 'Horsepower', 'align': 'right'\} is not of type 'string'.*" - + r"\{'text': 'Horsepower', 'align': 'right'\} is not of type 'array'", + inspect.cleandoc( + r"""'{'text': 'Horsepower', 'align': 'right'}' is an invalid value for `title`: + + {'text': 'Horsepower', 'align': 'right'} is not of type 'string' + {'text': 'Horsepower', 'align': 'right'} is not of type 'array'""" + ), ), ( chart_example_invalid_channel_and_condition, - r"schema.core.Encoding->encoding.*" - + r"Additional properties are not allowed \('invalidChannel' was unexpected\).*", + r"Additional properties are not allowed \('invalidChannel' was unexpected\)\n", ), ], ) From 750bfd129a6c27c02f54e8b1f052ded2018644ee Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Sat, 18 Feb 2023 11:11:57 -0800 Subject: [PATCH 13/23] Update old test message to remove what is no longer in the error message --- tests/utils/tests/test_schemapi.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/utils/tests/test_schemapi.py b/tests/utils/tests/test_schemapi.py index 3dc9028da..a5b66ae65 100644 --- a/tests/utils/tests/test_schemapi.py +++ b/tests/utils/tests/test_schemapi.py @@ -378,9 +378,7 @@ def test_schema_validation_error(): assert isinstance(the_err, SchemaValidationError) message = str(the_err) - assert message.startswith("Invalid specification") - assert "test_schemapi.MySchema->a" in message - assert "validating {!r}".format(the_err.validator) in message + assert "4 is not of type 'string'" in message assert the_err.message in message From 8e9e7c7807649ad52f589968b97c314987e4eaad Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Sat, 18 Feb 2023 11:15:32 -0800 Subject: [PATCH 14/23] Move invalid y option tests next to each other --- tests/utils/tests/test_schemapi.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/utils/tests/test_schemapi.py b/tests/utils/tests/test_schemapi.py index a5b66ae65..8fa671400 100644 --- a/tests/utils/tests/test_schemapi.py +++ b/tests/utils/tests/test_schemapi.py @@ -478,12 +478,6 @@ def chart_example_invalid_y_option_value_with_condition(): See the help for `X` to read the full description of these parameters""" ), ), - ( - chart_example_invalid_y_option_value, - r"'asdf' is an invalid value for `stack`.*" - + r"'asdf' is not one of \['zero', 'center', 'normalize'\].*" - + r"'asdf' is not of type 'null'.*'asdf' is not of type 'boolean'", - ), ( chart_example_layer, inspect.cleandoc( @@ -498,6 +492,16 @@ def chart_example_invalid_y_option_value_with_condition(): See the help for `VConcatChart` to read the full description of these parameters""" ), ), + ( + chart_example_invalid_y_option_value, + inspect.cleandoc( + r"""'asdf' is an invalid value for `stack`: + + 'asdf' is not one of \['zero', 'center', 'normalize'\] + 'asdf' is not of type 'null' + 'asdf' is not of type 'boolean'""" + ), + ), ( chart_example_invalid_y_option_value_with_condition, inspect.cleandoc( From 7a89d764581cb500066dc838d932d5d20433520e Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Sat, 18 Feb 2023 11:33:54 -0800 Subject: [PATCH 15/23] Black format --- altair/utils/schemapi.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/altair/utils/schemapi.py b/altair/utils/schemapi.py index 69152bebb..b73199413 100644 --- a/altair/utils/schemapi.py +++ b/altair/utils/schemapi.py @@ -164,7 +164,11 @@ def __init__(self, obj, err): def _format_params_as_table(param_dict_keys): """Format param names into a table so that they are easier to read""" param_names, name_lengths = zip( - *[(name, len(name)) for name in param_dict_keys if name not in ["kwds", "self"]] + *[ + (name, len(name)) + for name in param_dict_keys + if name not in ["kwds", "self"] + ] ) # Worst case scenario with the same longest param name in the same # row for all columns From 01c517fe796111b2ab26938572f6696cc2087e60 Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Sat, 18 Feb 2023 11:36:48 -0800 Subject: [PATCH 16/23] Update test to account for missing self --- tests/utils/tests/test_schemapi.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/utils/tests/test_schemapi.py b/tests/utils/tests/test_schemapi.py index 8fa671400..77b17299e 100644 --- a/tests/utils/tests/test_schemapi.py +++ b/tests/utils/tests/test_schemapi.py @@ -484,10 +484,10 @@ def chart_example_invalid_y_option_value_with_condition(): r"""`VConcatChart` has no parameter named 'width' Existing parameter names are: - self bounds datasets params title - vconcat center description resolve transform - autosize config name spacing usermeta - background data padding + vconcat center description params title + autosize config name resolve transform + background data padding spacing usermeta + bounds datasets See the help for `VConcatChart` to read the full description of these parameters""" ), From f9b4f3b46d6c9726fdd31a195b3242eb92f442ac Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Sat, 18 Feb 2023 12:07:59 -0800 Subject: [PATCH 17/23] Refine the message construction and move it to the else clause where it will be used --- altair/utils/schemapi.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/altair/utils/schemapi.py b/altair/utils/schemapi.py index b73199413..cd7978fe3 100644 --- a/altair/utils/schemapi.py +++ b/altair/utils/schemapi.py @@ -240,16 +240,6 @@ def __str__(self): for val in schema_path[:-1] if val not in (0, "properties", "additionalProperties", "patternProperties") ) - message = self.message - if self._additional_errors: - # The indentation here must match that of `cleandoc` below - additional_errors = "\n ".join( - [e.message for e in self._additional_errors] - ) - message = f"""'{self.instance}' is an invalid value for `{self.absolute_path[-1]}`: - - {message} - {additional_errors}""" # Output all existing parameters when an unknown parameter is specified if ( @@ -270,13 +260,27 @@ def __str__(self): See the help for `{}` to read the full description of these parameters """.format( altair_class, - message.split("('")[-1].split("'")[0], + self.message.split("('")[-1].split("'")[0], param_names_table, altair_class, ) ) # Use the default error message for all other cases than unknown parameter errors else: + message = self.message + # Add a summary line when parameters are passed an invalid value + # For example: "'asdf' is an invalid value for `stack` + if hasattr(vegalite, schema_path.split(".")[-1]) and self.absolute_path: + # The indentation here must match that of `cleandoc` below + message = f"""'{self.instance}' is an invalid value for `{self.absolute_path[-1]}`: + + {message}""" + + if self._additional_errors: + message += "\n " + "\n ".join( + [e.message for e in self._additional_errors] + ) + return inspect.cleandoc( """{} """.format( From 371843223930d90ab6b870677481bf792e139d4a Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Sat, 18 Feb 2023 12:26:52 -0800 Subject: [PATCH 18/23] Remove flake8 test for multline line strings with trailing whitespace --- tests/utils/tests/test_schemapi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/utils/tests/test_schemapi.py b/tests/utils/tests/test_schemapi.py index 77b17299e..5ed71ff0b 100644 --- a/tests/utils/tests/test_schemapi.py +++ b/tests/utils/tests/test_schemapi.py @@ -475,7 +475,7 @@ def chart_example_invalid_y_option_value_with_condition(): axis impute stack type bandPosition - See the help for `X` to read the full description of these parameters""" + See the help for `X` to read the full description of these parameters""" # noqa: W291 ), ), ( @@ -489,7 +489,7 @@ def chart_example_invalid_y_option_value_with_condition(): background data padding spacing usermeta bounds datasets - See the help for `VConcatChart` to read the full description of these parameters""" + See the help for `VConcatChart` to read the full description of these parameters""" # noqa: W291 ), ), ( From 3022f8c1f61cc81b4dd980c74b4ac2438ab54979 Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Sat, 18 Feb 2023 12:34:19 -0800 Subject: [PATCH 19/23] Add latest updates to source generator file as well --- tools/schemapi/schemapi.py | 59 ++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/tools/schemapi/schemapi.py b/tools/schemapi/schemapi.py index d94a5275f..759de2d5e 100644 --- a/tools/schemapi/schemapi.py +++ b/tools/schemapi/schemapi.py @@ -162,7 +162,11 @@ def __init__(self, obj, err): def _format_params_as_table(param_dict_keys): """Format param names into a table so that they are easier to read""" param_names, name_lengths = zip( - *[(name, len(name)) for name in param_dict_keys if name != "kwds"] + *[ + (name, len(name)) + for name in param_dict_keys + if name not in ["kwds", "self"] + ] ) # Worst case scenario with the same longest param name in the same # row for all columns @@ -234,45 +238,52 @@ def __str__(self): for val in schema_path[:-1] if val not in (0, "properties", "additionalProperties", "patternProperties") ) - message = self.message - if self._additional_errors: - message += "\n " + "\n ".join( - [e.message for e in self._additional_errors] - ) - if hasattr(vegalite, schema_path.split(".")[-1]): - altair_class = "altair." + schema_path.split(".")[-1] + # Output all existing parameters when an unknown parameter is specified + if ( + hasattr(vegalite, schema_path.split(".")[-1]) + and self.validator == "additionalProperties" + ): + altair_class = schema_path.split(".")[-1] vegalite_core_class = getattr(vegalite, schema_path.split(".")[-1]) param_dict_keys = inspect.signature(vegalite_core_class).parameters.keys() param_names_table = self._format_params_as_table(param_dict_keys) - # cleandoc removes multiline string indentation in the output + # `cleandoc` removes multiline string indentation in the output return inspect.cleandoc( - """Invalid specification - - {}, validating {!r} - - {} has no parameter named {!r} + """`{}` has no parameter named {!r} Existing parameter names are: {} - See the help for {} to read the full description of these parameters + See the help for `{}` to read the full description of these parameters """.format( - schema_path, - self.validator, altair_class, - message.split("('")[-1].split("'")[0], + self.message.split("('")[-1].split("'")[0], param_names_table, altair_class, ) ) - # Fall back on the less informative error message + # Use the default error message for all other cases than unknown parameter errors else: - return """Invalid specification - {}, validating {!r} - {} - """.format( - schema_path, self.validator, message + message = self.message + # Add a summary line when parameters are passed an invalid value + # For example: "'asdf' is an invalid value for `stack` + if hasattr(vegalite, schema_path.split(".")[-1]) and self.absolute_path: + # The indentation here must match that of `cleandoc` below + message = f"""'{self.instance}' is an invalid value for `{self.absolute_path[-1]}`: + + {message}""" + + if self._additional_errors: + message += "\n " + "\n ".join( + [e.message for e in self._additional_errors] + ) + + return inspect.cleandoc( + """{} + """.format( + message + ) ) From b9949599e50a6b0d69e6a1f452273a3e7193a877 Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Sun, 19 Feb 2023 14:47:31 -0800 Subject: [PATCH 20/23] Remove redundant schema_path variable and allow unknown encodings params to trigger the parameter table Co-authored-by: Stefan Binder --- altair/utils/schemapi.py | 22 +++++----------------- tests/utils/tests/test_schemapi.py | 15 ++++++++++++++- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/altair/utils/schemapi.py b/altair/utils/schemapi.py index 66fe39898..1e2b04063 100644 --- a/altair/utils/schemapi.py +++ b/altair/utils/schemapi.py @@ -233,22 +233,10 @@ def __str__(self): # back on the class of the top-level object which created # the SchemaValidationError cls = self.obj.__class__ - schema_path = ["{}.{}".format(cls.__module__, cls.__name__)] - schema_path.extend(self.schema_path) - schema_path = "->".join( - str(val) - for val in schema_path[:-1] - if val not in (0, "properties", "additionalProperties", "patternProperties") - ) # Output all existing parameters when an unknown parameter is specified - if ( - hasattr(vegalite, schema_path.split(".")[-1]) - and self.validator == "additionalProperties" - ): - altair_class = schema_path.split(".")[-1] - vegalite_core_class = getattr(vegalite, schema_path.split(".")[-1]) - param_dict_keys = inspect.signature(vegalite_core_class).parameters.keys() + if self.validator == "additionalProperties": + param_dict_keys = inspect.signature(cls).parameters.keys() param_names_table = self._format_params_as_table(param_dict_keys) # `cleandoc` removes multiline string indentation in the output @@ -259,10 +247,10 @@ def __str__(self): {} See the help for `{}` to read the full description of these parameters """.format( - altair_class, + cls.__name__, self.message.split("('")[-1].split("'")[0], param_names_table, - altair_class, + cls.__name__, ) ) # Use the default error message for all other cases than unknown parameter errors @@ -270,7 +258,7 @@ def __str__(self): message = self.message # Add a summary line when parameters are passed an invalid value # For example: "'asdf' is an invalid value for `stack` - if hasattr(vegalite, schema_path.split(".")[-1]) and self.absolute_path: + if self.absolute_path: # The indentation here must match that of `cleandoc` below message = f"""'{self.instance}' is an invalid value for `{self.absolute_path[-1]}`: diff --git a/tests/utils/tests/test_schemapi.py b/tests/utils/tests/test_schemapi.py index d38c458fb..bcbc3330b 100644 --- a/tests/utils/tests/test_schemapi.py +++ b/tests/utils/tests/test_schemapi.py @@ -523,7 +523,20 @@ def chart_example_invalid_y_option_value_with_condition(): ), ( chart_example_invalid_channel_and_condition, - r"Additional properties are not allowed \('invalidChannel' was unexpected\)\n", + inspect.cleandoc( + r"""`Encoding` has no parameter named 'invalidChannel' + + Existing parameter names are: + angle key order strokeDash tooltip xOffset + color latitude radius strokeOpacity url y + description latitude2 radius2 strokeWidth x y2 + detail longitude shape text x2 yError + fill longitude2 size theta xError yError2 + fillOpacity opacity stroke theta2 xError2 yOffset + href + + See the help for `Encoding` to read the full description of these parameters""" # noqa: W291 + ), ), ], ) From afd614b2ff4e399077d4c1ba66cf551de688c5f5 Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Sun, 19 Feb 2023 15:17:48 -0800 Subject: [PATCH 21/23] Remove redundant assert --- tests/utils/tests/test_schemapi.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/utils/tests/test_schemapi.py b/tests/utils/tests/test_schemapi.py index bcbc3330b..9edc7695a 100644 --- a/tests/utils/tests/test_schemapi.py +++ b/tests/utils/tests/test_schemapi.py @@ -378,7 +378,6 @@ def test_schema_validation_error(): assert isinstance(the_err, SchemaValidationError) message = str(the_err) - assert "4 is not of type 'string'" in message assert the_err.message in message From 4e8bac5dfcdb2f62717f098a913e32da9ea78c8e Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Sun, 19 Feb 2023 15:32:43 -0800 Subject: [PATCH 22/23] Add latest updates to source generator file as well --- tools/schemapi/schemapi.py | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/tools/schemapi/schemapi.py b/tools/schemapi/schemapi.py index 84cd7d585..1b84cd247 100644 --- a/tools/schemapi/schemapi.py +++ b/tools/schemapi/schemapi.py @@ -231,22 +231,10 @@ def __str__(self): # back on the class of the top-level object which created # the SchemaValidationError cls = self.obj.__class__ - schema_path = ["{}.{}".format(cls.__module__, cls.__name__)] - schema_path.extend(self.schema_path) - schema_path = "->".join( - str(val) - for val in schema_path[:-1] - if val not in (0, "properties", "additionalProperties", "patternProperties") - ) # Output all existing parameters when an unknown parameter is specified - if ( - hasattr(vegalite, schema_path.split(".")[-1]) - and self.validator == "additionalProperties" - ): - altair_class = schema_path.split(".")[-1] - vegalite_core_class = getattr(vegalite, schema_path.split(".")[-1]) - param_dict_keys = inspect.signature(vegalite_core_class).parameters.keys() + if self.validator == "additionalProperties": + param_dict_keys = inspect.signature(cls).parameters.keys() param_names_table = self._format_params_as_table(param_dict_keys) # `cleandoc` removes multiline string indentation in the output @@ -257,10 +245,10 @@ def __str__(self): {} See the help for `{}` to read the full description of these parameters """.format( - altair_class, + cls.__name__, self.message.split("('")[-1].split("'")[0], param_names_table, - altair_class, + cls.__name__, ) ) # Use the default error message for all other cases than unknown parameter errors @@ -268,7 +256,7 @@ def __str__(self): message = self.message # Add a summary line when parameters are passed an invalid value # For example: "'asdf' is an invalid value for `stack` - if hasattr(vegalite, schema_path.split(".")[-1]) and self.absolute_path: + if self.absolute_path: # The indentation here must match that of `cleandoc` below message = f"""'{self.instance}' is an invalid value for `{self.absolute_path[-1]}`: From 187c81770ff5997fbc5c31c61a81af4a15a039db Mon Sep 17 00:00:00 2001 From: Joel Ostblom Date: Mon, 20 Feb 2023 15:23:29 -0800 Subject: [PATCH 23/23] Include changes to tests in tools code generation file --- tools/schemapi/tests/test_schemapi.py | 79 ++++++++++++++++++++------- 1 file changed, 59 insertions(+), 20 deletions(-) diff --git a/tools/schemapi/tests/test_schemapi.py b/tools/schemapi/tests/test_schemapi.py index f8ad19c9c..bb3bbae7f 100644 --- a/tools/schemapi/tests/test_schemapi.py +++ b/tools/schemapi/tests/test_schemapi.py @@ -1,5 +1,6 @@ import copy import io +import inspect import json import jsonschema import re @@ -375,9 +376,6 @@ def test_schema_validation_error(): assert isinstance(the_err, SchemaValidationError) message = str(the_err) - assert message.startswith("Invalid specification") - assert "test_schemapi.MySchema->a" in message - assert "validating {!r}".format(the_err.validator) in message assert the_err.message in message @@ -465,36 +463,77 @@ def chart_example_invalid_y_option_value_with_condition(): [ ( chart_example_invalid_y_option, - r"schema.channels.X.*" - + r"Additional properties are not allowed \('unknown' was unexpected\)", + inspect.cleandoc( + r"""`X` has no parameter named 'unknown' + + Existing parameter names are: + shorthand bin scale timeUnit + aggregate field sort title + axis impute stack type + bandPosition + + See the help for `X` to read the full description of these parameters""" # noqa: W291 + ), ), ( - chart_example_invalid_y_option_value, - r"schema.channels.Y.*" - + r"'asdf' is not one of \['zero', 'center', 'normalize'\].*" - + r"'asdf' is not of type 'null'.*'asdf' is not of type 'boolean'", + chart_example_layer, + inspect.cleandoc( + r"""`VConcatChart` has no parameter named 'width' + + Existing parameter names are: + vconcat center description params title + autosize config name resolve transform + background data padding spacing usermeta + bounds datasets + + See the help for `VConcatChart` to read the full description of these parameters""" # noqa: W291 + ), ), ( - chart_example_layer, - r"api.VConcatChart.*" - + r"Additional properties are not allowed \('width' was unexpected\)", + chart_example_invalid_y_option_value, + inspect.cleandoc( + r"""'asdf' is an invalid value for `stack`: + + 'asdf' is not one of \['zero', 'center', 'normalize'\] + 'asdf' is not of type 'null' + 'asdf' is not of type 'boolean'""" + ), ), ( chart_example_invalid_y_option_value_with_condition, - r"schema.channels.Y.*" - + r"'asdf' is not one of \['zero', 'center', 'normalize'\].*" - + r"'asdf' is not of type 'null'.*'asdf' is not of type 'boolean'", + inspect.cleandoc( + r"""'asdf' is an invalid value for `stack`: + + 'asdf' is not one of \['zero', 'center', 'normalize'\] + 'asdf' is not of type 'null' + 'asdf' is not of type 'boolean'""" + ), ), ( chart_example_hconcat, - r"schema.core.TitleParams.*" - + r"\{'text': 'Horsepower', 'align': 'right'\} is not of type 'string'.*" - + r"\{'text': 'Horsepower', 'align': 'right'\} is not of type 'array'", + inspect.cleandoc( + r"""'{'text': 'Horsepower', 'align': 'right'}' is an invalid value for `title`: + + {'text': 'Horsepower', 'align': 'right'} is not of type 'string' + {'text': 'Horsepower', 'align': 'right'} is not of type 'array'""" + ), ), ( chart_example_invalid_channel_and_condition, - r"schema.core.Encoding->encoding.*" - + r"Additional properties are not allowed \('invalidChannel' was unexpected\)", + inspect.cleandoc( + r"""`Encoding` has no parameter named 'invalidChannel' + + Existing parameter names are: + angle key order strokeDash tooltip xOffset + color latitude radius strokeOpacity url y + description latitude2 radius2 strokeWidth x y2 + detail longitude shape text x2 yError + fill longitude2 size theta xError yError2 + fillOpacity opacity stroke theta2 xError2 yOffset + href + + See the help for `Encoding` to read the full description of these parameters""" # noqa: W291 + ), ), ], )