diff --git a/omegaconf/base.py b/omegaconf/base.py index bc2f50bfd..d76e557ad 100644 --- a/omegaconf/base.py +++ b/omegaconf/base.py @@ -20,6 +20,8 @@ InterpolationKeyError, InterpolationResolutionError, InterpolationToMissingValueError, + InterpolationValidationError, + KeyValidationError, MissingMandatoryValue, OmegaConfBaseException, UnsupportedInterpolationType, @@ -425,7 +427,12 @@ def _resolve_interpolation_from_parse_tree( conv_value = value.validate_and_convert(res_value) except ValidationError as e: if throw_on_resolution_failure: - self._format_and_raise(key=key, value=res_value, cause=e) + self._format_and_raise( + key=key, + value=res_value, + cause=e, + type_override=InterpolationValidationError, + ) return None # If the converted value is of the same type, it means that no conversion @@ -437,14 +444,24 @@ def _resolve_interpolation_from_parse_tree( if must_wrap: assert parent is None or isinstance(parent, BaseContainer) - return _node_wrap( - type_=value._metadata.ref_type, - parent=parent, - is_optional=value._metadata.optional, - value=resolved, - key=key, - ref_type=value._metadata.ref_type, - ) + try: + return _node_wrap( + type_=value._metadata.ref_type, + parent=parent, + is_optional=value._metadata.optional, + value=resolved, + key=key, + ref_type=value._metadata.ref_type, + ) + except (KeyValidationError, ValidationError) as e: + if throw_on_resolution_failure: + self._format_and_raise( + key=key, + value=resolved, + cause=e, + type_override=InterpolationValidationError, + ) + return None else: assert isinstance(resolved, Node) return resolved diff --git a/omegaconf/errors.py b/omegaconf/errors.py index a7a2cf858..591ee45dd 100644 --- a/omegaconf/errors.py +++ b/omegaconf/errors.py @@ -81,6 +81,12 @@ class InterpolationToMissingValueError(InterpolationResolutionError): """ +class InterpolationValidationError(InterpolationResolutionError): + """ + Thrown when the result of an interpolation fails the validation step. + """ + + class ConfigKeyError(OmegaConfBaseException, KeyError): """ Thrown from DictConfig when a regular dict access would have caused a KeyError. diff --git a/tests/test_interpolation.py b/tests/test_interpolation.py index 55cd7f1e3..8d28ad570 100644 --- a/tests/test_interpolation.py +++ b/tests/test_interpolation.py @@ -25,7 +25,7 @@ GrammarParseError, InterpolationKeyError, InterpolationResolutionError, - KeyValidationError, + InterpolationValidationError, OmegaConfBaseException, UnsupportedInterpolationType, ) @@ -843,7 +843,7 @@ def drop_last(s: str) -> str: User(name="Bond", age=SI("${cast:str,seven}")), "age", pytest.raises( - ValidationError, + InterpolationValidationError, match=re.escape( dedent( """\ @@ -859,7 +859,7 @@ def drop_last(s: str) -> str: User(name="Bond", age=SI("${name}")), "age", pytest.raises( - ValidationError, + InterpolationValidationError, match=re.escape( dedent( """\ @@ -875,7 +875,7 @@ def drop_last(s: str) -> str: StructuredWithMissing(opt_num=None, num=II("opt_num")), "num", pytest.raises( - ValidationError, + InterpolationValidationError, match=re.escape("Non optional field cannot be assigned None"), ), id="non_optional_node_interpolation", @@ -884,7 +884,7 @@ def drop_last(s: str) -> str: SubscriptedList(list=SI("${identity:[a, b]}")), "list", pytest.raises( - ValidationError, + InterpolationValidationError, match=re.escape("Value 'a' could not be converted to Integer"), ), id="list_type_mismatch", @@ -893,7 +893,7 @@ def drop_last(s: str) -> str: MissingDict(dict=SI("${identity:{0: b, 1: d}}")), "dict", pytest.raises( - KeyValidationError, + InterpolationValidationError, match=re.escape("Key 0 (int) is incompatible with (str)"), ), id="dict_key_type_mismatch", @@ -905,7 +905,7 @@ def test_interpolation_type_validated_error( key: str, expected_error: Any, restore_resolvers: Any, -) -> Any: +) -> None: def cast(t: Any, v: Any) -> Any: return {"str": str, "int": int}[t](v) # cast `v` to type `t` @@ -917,6 +917,8 @@ def cast(t: Any, v: Any) -> Any: with expected_error: cfg[key] + assert OmegaConf.select(cfg, key, throw_on_resolution_failure=False) is None + def test_type_validation_error_no_throw() -> None: cfg = OmegaConf.structured(User(name="Bond", age=SI("${name}")))