Repository: omry/omegaconf

Issue:
Double crap in error message

```python
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
from dataclasses import dataclass

import hydra
from hydra.core.config_store import ConfigStore


@dataclass
class MySQLConfig:
    driver: str = "mysql"
    host: str = "localhost"
    port: int = 3306
    user: str = "omry"
    password: str = "secret"


cfg_store = ConfigStore.instance()
# Registering the Config class with the name 'config'
cfg_store.store(node=MySQLConfig, name="config")


@hydra.main(config_name="config")
def my_app(cfg: MySQLConfig) -> None:
    # The real type of cfg is DictConfig, but we lie a little to get static type checking.
    # If it swims like a duck and quacks like a duck, it's a "duck".
    print(
        f"Connecting to {cfg.drover} at {cfg.host}:{cfg.port},"
        f" user={cfg.user}, password={cfg.password}"
    )


if __name__ == "__main__":
    my_app()

$ python examples/structured_configs_tutorial/1_basic/my_app.py
...
omegaconf.errors.ConfigAttributeError: Key 'drover' not in 'MySQLConfig'
        full_key: drover
        reference_type=Optional[dict]
        object_type=MySQLConfig

        full_key: drover
        reference_type=Optional[dict]
        object_type=MySQLConfig
```

Discussion:

Patch:
diff --git a/omegaconf/_utils.py b/omegaconf/_utils.py
index 5879d6d..b42271e 100644
--- a/omegaconf/_utils.py
+++ b/omegaconf/_utils.py
@@ -1,3 +1,4 @@
+import copy
 import os
 import re
 import string
@@ -438,6 +439,23 @@ def format_and_raise(
     from omegaconf import OmegaConf
     from omegaconf.base import Node
 
+    def raise_(ex: Exception, cause: Exception) -> None:
+        # Set the environment variable OC_CAUSE=1 to get a stacktrace that includes the
+        # causing exception.
+        full_backtrace = "OC_CAUSE" in os.environ and os.environ["OC_CAUSE"] == "1"
+        if full_backtrace:
+            ex.__cause__ = cause
+        else:
+            ex.__cause__ = None
+        raise ex
+
+    if isinstance(cause, OmegaConfBaseException) and cause._initialized:
+        ex = cause
+        if type_override is not None:
+            ex = type_override(str(cause))
+            ex.__dict__ = copy.deepcopy(cause.__dict__)
+        raise_(ex, cause)
+
     object_type: Optional[Type[Any]]
     object_type_str: Optional[str] = None
     ref_type: Optional[Type[Any]]
@@ -497,25 +515,18 @@ def format_and_raise(
     ex = exception_type(f"{message}")
     if issubclass(exception_type, OmegaConfBaseException):
         ex._initialized = True
+        ex.msg = message
         ex.parent_node = node
         ex.child_node = child_node
         ex.key = key
         ex.full_key = full_key
         ex.value = value
-        ex.msg = msg
         ex.object_type = object_type
         ex.object_type_str = object_type_str
         ex.ref_type = ref_type
         ex.ref_type_str = ref_type_str
 
-    # Set the environment variable OC_CAUSE=1 to get a stacktrace that includes the
-    # causing exception.
-    full_backtrace = "OC_CAUSE" in os.environ and os.environ["OC_CAUSE"] == "1"
-    if full_backtrace:
-        ex.__cause__ = cause
-    else:
-        ex.__cause__ = None
-    raise ex
+    raise_(ex, cause)
 
 
 def type_str(t: Any) -> str:
diff --git a/omegaconf/basecontainer.py b/omegaconf/basecontainer.py
index 9a25f07..f074827 100644
--- a/omegaconf/basecontainer.py
+++ b/omegaconf/basecontainer.py
@@ -20,12 +20,7 @@ from ._utils import (
     is_structured_config,
 )
 from .base import Container, ContainerMetadata, Node
-from .errors import (
-    MissingMandatoryValue,
-    OmegaConfBaseException,
-    ReadonlyConfigError,
-    ValidationError,
-)
+from .errors import MissingMandatoryValue, ReadonlyConfigError, ValidationError
 
 DEFAULT_VALUE_MARKER: Any = str("__DEFAULT_VALUE_MARKER__")
 
@@ -291,10 +286,7 @@ class BaseContainer(Container, ABC):
             # recursively correct the parent hierarchy after the merge
             self._re_parent()
         except Exception as e:
-            if isinstance(e, OmegaConfBaseException) and e._initialized:
-                raise e
-            else:
-                self._format_and_raise(key=None, value=None, cause=e)
+            self._format_and_raise(key=None, value=None, cause=e)
 
     # noinspection PyProtectedMember
     def _set_item_impl(self, key: Any, value: Any) -> None:
diff --git a/omegaconf/dictconfig.py b/omegaconf/dictconfig.py
index 137221c..82a1636 100644
--- a/omegaconf/dictconfig.py
+++ b/omegaconf/dictconfig.py
@@ -149,10 +149,7 @@ class DictConfig(BaseContainer, MutableMapping[str, Any]):
 
         # validate get
         if key is not None:
-            try:
-                self._validate_get(key, value)
-            except AttributeError as e:
-                raise ConfigAttributeError(f"Error setting $KEY=$VALUE : {e}")
+            self._validate_get(key, value)
 
         target: Optional[Node]
         if key is None:
@@ -363,11 +360,7 @@ class DictConfig(BaseContainer, MutableMapping[str, Any]):
         try:
             key = self._validate_and_normalize_key(key)
             if self._get_flag("readonly"):
-                self._format_and_raise(
-                    key=key,
-                    value=None,
-                    cause=ReadonlyConfigError("Cannot pop from read-only node"),
-                )
+                raise ReadonlyConfigError("Cannot pop from read-only node")
 
             node = self._get_node(key=key)
             if node is not None:
diff --git a/omegaconf/errors.py b/omegaconf/errors.py
index 6efe646..fc2afd1 100644
--- a/omegaconf/errors.py
+++ b/omegaconf/errors.py
@@ -11,9 +11,9 @@ class OmegaConfBaseException(Exception):
     msg: Optional[str]
     cause: Optional[Exception]
     object_type: Optional[Type[Any]]
-    object_type_str: str
+    object_type_str: Optional[str]
     ref_type: Optional[Type[Any]]
-    ref_type_str: str
+    ref_type_str: Optional[str]
 
     _initialized: bool = False
 


Test patch:
diff --git a/tests/test_errors.py b/tests/test_errors.py
index e51f15f..2649724 100644
--- a/tests/test_errors.py
+++ b/tests/test_errors.py
@@ -22,6 +22,7 @@ from omegaconf.errors import (
     ConfigValueError,
     KeyValidationError,
     MissingMandatoryValue,
+    OmegaConfBaseException,
 )
 
 from . import (
@@ -52,6 +53,7 @@ class Expected:
     parent_node: Any = lambda cfg: cfg
     object_type_str: Optional[str] = "AUTO"
     ref_type_str: Optional[str] = "AUTO"
+    num_lines: int = 4
 
     def finalize(self, cfg: Any) -> None:
         if self.object_type == "AUTO":
@@ -271,7 +273,7 @@ params = [
             create=lambda: create_struct({"foo": "bar"}),
             op=lambda cfg: cfg.__setitem__("zoo", "zonk"),
             exception_type=KeyError,
-            msg="Error setting zoo=zonk : Key 'zoo' in not in struct",
+            msg="Key 'zoo' in not in struct",
             key="zoo",
         ),
         id="dict,struct:setitem_on_none_existing_key",
@@ -294,6 +296,7 @@ params = [
             exception_type=KeyValidationError,
             msg="Key 'foo' is incompatible with (Color)",
             key="foo",
+            num_lines=7,
         ),
         id="dict,reftype=Dict[Color,str]:,getitem_str_key",
     ),
@@ -420,6 +423,7 @@ params = [
             msg="Union types are not supported:\nx: Union[int, str]",
             object_type_str=None,
             ref_type_str=None,
+            num_lines=5,
         ),
         id="structured:create_with_union_error",
     ),
@@ -904,14 +908,16 @@ def test_errors(expected: Expected, monkeypatch: Any) -> None:
     with pytest.raises(expected.exception_type, match=re.escape(msg)) as einfo:
         try:
             expected.op(cfg)
-        except Exception as e:
+        except Exception:
             # helps in debugging
-            raise e from e
+            raise
     ex = einfo.value
 
     assert ex.object_type == expected.object_type
     assert ex.key == expected.key
     if not expected.low_level:
+        if isinstance(ex, OmegaConfBaseException):
+            assert str(ex).count("\n") == expected.num_lines
         assert ex.parent_node == expected.parent_node(cfg)
         assert ex.child_node == expected.child_node(cfg)
         assert ex.full_key == expected.full_key