Repository: facebookresearch/hydra

Issue:
Instantiation exception also raises an exception sometimes
# 🐛 Bug
## Description

When there's an instantiation exception, raising a chained exception sometimes also fails because it doesn't take an argument:

https://github.com/facebookresearch/hydra/blob/9759f87673be1d41fe33279e1b7980f8efb38b19/hydra/_internal/instantiate/_instantiate2.py#L63-L66

## Checklist
- [x] I checked on the latest version of Hydra
- [ ] I created a minimal repro (See [this](https://stackoverflow.com/help/minimal-reproducible-example) for tips).

## To reproduce

Try to instantiate an object such that it raises an exception that doesn't take any argument.

## Expected Behavior

Maybe it should try to show another type of exception instead? Or chaining (`from e`).

## System information
- **Hydra Version** :  1.1
- **Python version** : 3.8
- **Virtual environment type and version** : conda 4.11
- **Operating system** : Ubuntu 18.04

## Additional context
Add any other context about the problem here.



Discussion:
Hi @bryant1410, thanks for reporting this.

Here is a minimal repro:
```python
# module.py
class MyException(Exception):
    def __init__(self):
        """Init method taking no argument"""
        pass

def foo():
    raise MyException()
```
```python
# run.py
from hydra.utils import instantiate

instantiate({"_target_": "module.foo"})
```
```text
$ python run.py
Traceback (most recent call last):
  File "/Users/jasha10/hydra.git/hydra/_internal/instantiate/_instantiate2.py", line 62, in _call_target
    return _target_(*args, **kwargs)
  File "/Users/jasha10/hydra_tmp/1911/module.py", line 8, in foo
    raise MyException()
module.MyException

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/jasha10/hydra_tmp/1911/run.py", line 4, in <module>
    instantiate({"_target_": "module.foo"})
  File "/Users/jasha10/hydra.git/hydra/_internal/instantiate/_instantiate2.py", line 180, in instantiate
    return instantiate_node(config, *args, recursive=_recursive_, convert=_convert_)
  File "/Users/jasha10/hydra.git/hydra/_internal/instantiate/_instantiate2.py", line 249, in instantiate_node
    return _call_target(_target_, *args, **kwargs)
  File "/Users/jasha10/hydra.git/hydra/_internal/instantiate/_instantiate2.py", line 64, in _call_target
    raise type(e)(
TypeError: __init__() takes 1 positional argument but 2 were given
```
I like your proposed idea of exception chaining.

Patch:
diff --git a/hydra/_internal/instantiate/_instantiate2.py b/hydra/_internal/instantiate/_instantiate2.py
--- a/hydra/_internal/instantiate/_instantiate2.py
+++ b/hydra/_internal/instantiate/_instantiate2.py
@@ -60,14 +60,20 @@ def _call_target(_target_: Callable, _partial_: bool, *args, **kwargs) -> Any:
         for v in kwargs.values():
             if OmegaConf.is_config(v):
                 v._set_parent(None)
-        if _partial_:
-            return functools.partial(_target_, *args, **kwargs)
-        return _target_(*args, **kwargs)
     except Exception as e:
         raise type(e)(
             f"Error instantiating '{_convert_target_to_string(_target_)}' : {e}"
         ).with_traceback(sys.exc_info()[2])
 
+    try:
+        if _partial_:
+            return functools.partial(_target_, *args, **kwargs)
+        return _target_(*args, **kwargs)
+    except Exception as e:
+        raise InstantiationException(
+            f"Error instantiating '{_convert_target_to_string(_target_)}' : {repr(e)}"
+        ) from e
+
 
 def _convert_target_to_string(t: Any) -> Any:
     if callable(t):


Test patch:
diff --git a/tests/instantiate/__init__.py b/tests/instantiate/__init__.py
--- a/tests/instantiate/__init__.py
+++ b/tests/instantiate/__init__.py
@@ -3,7 +3,7 @@
 import collections.abc
 from dataclasses import dataclass
 from functools import partial
-from typing import Any, Dict, List, Optional, Tuple
+from typing import Any, Dict, List, NoReturn, Optional, Tuple
 
 from omegaconf import MISSING, DictConfig, ListConfig
 
@@ -93,6 +93,16 @@ def module_function2() -> str:
     return "fn return"
 
 
+class ExceptionTakingNoArgument(Exception):
+    def __init__(self) -> None:
+        """Init method taking only one argument (self)"""
+        super().__init__("Err message")
+
+
+def raise_exception_taking_no_argument() -> NoReturn:
+    raise ExceptionTakingNoArgument()
+
+
 @dataclass
 class AClass:
     a: Any
diff --git a/tests/instantiate/test_instantiate.py b/tests/instantiate/test_instantiate.py
--- a/tests/instantiate/test_instantiate.py
+++ b/tests/instantiate/test_instantiate.py
@@ -460,7 +460,10 @@ def test_class_instantiate_omegaconf_node(instantiate_func: Any, config: Any) ->
 
 @mark.parametrize("src", [{"_target_": "tests.instantiate.Adam"}])
 def test_instantiate_adam(instantiate_func: Any, config: Any) -> None:
-    with raises(TypeError):
+    with raises(
+        InstantiationException,
+        match=r"Error instantiating 'tests\.instantiate\.Adam' : TypeError\(.*\)",
+    ):
         # can't instantiate without passing params
         instantiate_func(config)
 
@@ -501,7 +504,10 @@ def gen() -> Any:
 def test_instantiate_adam_conf(
     instantiate_func: Any, is_partial: bool, expected_params: Any
 ) -> None:
-    with raises(TypeError):
+    with raises(
+        InstantiationException,
+        match=r"Error instantiating 'tests\.instantiate\.Adam' : TypeError\(.*\)",
+    ):
         # can't instantiate without passing params
         instantiate_func(AdamConf())
 
@@ -556,11 +562,26 @@ def test_instantiate_bad_adam_conf(instantiate_func: Any, recwarn: Any) -> None:
 
 def test_instantiate_with_missing_module(instantiate_func: Any) -> None:
 
+    _target_ = "tests.instantiate.ClassWithMissingModule"
     with raises(
-        ModuleNotFoundError, match=re.escape("No module named 'some_missing_module'")
+        InstantiationException,
+        match=re.escape(f"Error instantiating '{_target_}' : ")
+        + r"ModuleNotFoundError\(\"No module named 'some_missing_module'\",?\)",
     ):
         # can't instantiate when importing a missing module
-        instantiate_func({"_target_": "tests.instantiate.ClassWithMissingModule"})
+        instantiate_func({"_target_": _target_})
+
+
+def test_instantiate_target_raising_exception_taking_no_arguments(
+    instantiate_func: Any,
+) -> None:
+    _target_ = "tests.instantiate.raise_exception_taking_no_argument"
+    with raises(
+        InstantiationException,
+        match=re.escape(f"Error instantiating '{_target_}' : ")
+        + r"ExceptionTakingNoArgument\('Err message',?\)",
+    ):
+        instantiate_func({}, _target_=_target_)
 
 
 @mark.parametrize("is_partial", [True, False])