|
20 | 20 | import inspect
|
21 | 21 | import warnings
|
22 | 22 | from collections import OrderedDict, abc
|
| 23 | +from difflib import get_close_matches |
23 | 24 | from typing import (
|
24 | 25 | TYPE_CHECKING,
|
25 | 26 | Any,
|
@@ -162,9 +163,12 @@ def clean_node(node: str) -> tuple[str, int]:
|
162 | 163 | return host.lower(), port
|
163 | 164 |
|
164 | 165 |
|
165 |
| -def raise_config_error(key: str, dummy: Any) -> NoReturn: |
| 166 | +def raise_config_error(key: str, suggestions: Optional[list] = None) -> NoReturn: |
166 | 167 | """Raise ConfigurationError with the given key name."""
|
167 |
| - raise ConfigurationError(f"Unknown option {key}") |
| 168 | + msg = f"Unknown option: {key}." |
| 169 | + if suggestions: |
| 170 | + msg += f" Did you mean one of ({', '.join(suggestions)}) or maybe a camelCase version of one? Refer to docstring." |
| 171 | + raise ConfigurationError(msg) |
168 | 172 |
|
169 | 173 |
|
170 | 174 | # Mapping of URI uuid representation options to valid subtypes.
|
@@ -810,14 +814,24 @@ def validate_auth_option(option: str, value: Any) -> tuple[str, Any]:
|
810 | 814 | """Validate optional authentication parameters."""
|
811 | 815 | lower, value = validate(option, value)
|
812 | 816 | if lower not in _AUTH_OPTIONS:
|
813 |
| - raise ConfigurationError(f"Unknown authentication option: {option}") |
| 817 | + raise ConfigurationError(f"Unknown option: {option}. Must be in {_AUTH_OPTIONS}") |
814 | 818 | return option, value
|
815 | 819 |
|
816 | 820 |
|
| 821 | +def _get_validator( |
| 822 | + key: str, validators: dict[str, Callable[[Any, Any], Any]], normed_key: Optional[str] = None |
| 823 | +) -> Callable: |
| 824 | + normed_key = normed_key or key |
| 825 | + try: |
| 826 | + return validators[normed_key] |
| 827 | + except KeyError: |
| 828 | + suggestions = get_close_matches(normed_key, validators, cutoff=0.2) |
| 829 | + raise_config_error(key, suggestions) |
| 830 | + |
| 831 | + |
817 | 832 | def validate(option: str, value: Any) -> tuple[str, Any]:
|
818 | 833 | """Generic validation function."""
|
819 |
| - lower = option.lower() |
820 |
| - validator = VALIDATORS.get(lower, raise_config_error) |
| 834 | + validator = _get_validator(option, VALIDATORS, normed_key=option.lower()) |
821 | 835 | value = validator(option, value)
|
822 | 836 | return option, value
|
823 | 837 |
|
@@ -855,15 +869,15 @@ def get_setter_key(x: str) -> str:
|
855 | 869 | for opt, value in options.items():
|
856 | 870 | normed_key = get_normed_key(opt)
|
857 | 871 | try:
|
858 |
| - validator = URI_OPTIONS_VALIDATOR_MAP.get(normed_key, raise_config_error) |
859 |
| - value = validator(opt, value) # noqa: PLW2901 |
| 872 | + validator = _get_validator(opt, URI_OPTIONS_VALIDATOR_MAP, normed_key=normed_key) |
| 873 | + validated = validator(opt, value) |
860 | 874 | except (ValueError, TypeError, ConfigurationError) as exc:
|
861 | 875 | if warn:
|
862 | 876 | warnings.warn(str(exc), stacklevel=2)
|
863 | 877 | else:
|
864 | 878 | raise
|
865 | 879 | else:
|
866 |
| - validated_options[get_setter_key(normed_key)] = value |
| 880 | + validated_options[get_setter_key(normed_key)] = validated |
867 | 881 | return validated_options
|
868 | 882 |
|
869 | 883 |
|
|
0 commit comments