Repository: mit-ll-responsible-ai/hydra-zen

Issue:
Typing Error for `builds` using ART
I was hoping to make a simple example but I'm not sure what is causing the error:

```python
from art.estimators.classification import PyTorchClassifier

builds(PyTorchClassifier)  # the config is actually incomplete but it never gets to that error
```

error:

```python
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-81-0ae74d8b49b2> in <module>
----> 1 builds(PyTorchClassifier)

~/projects/raiden/hydra_zen/src/hydra_zen/structured_configs/_implementations.py in builds(target, populate_full_signature, hydra_partial, hydra_recursive, hydra_convert, frozen, dataclass_name, builds_bases, *pos_args, **kwargs_for_target)
    645             # target is class object...
    646             # calling `get_type_hints(target)` returns empty dict
--> 647             type_hints = get_type_hints(target.__init__)
    648         else:
    649             type_hints = get_type_hints(target)

~/.conda/envs/trusted_analytics/lib/python3.8/typing.py in get_type_hints(obj, globalns, localns)
   1262         if isinstance(value, str):
   1263             value = ForwardRef(value)
-> 1264         value = _eval_type(value, globalns, localns)
   1265         if name in defaults and defaults[name] is None:
   1266             value = Optional[value]

~/.conda/envs/trusted_analytics/lib/python3.8/typing.py in _eval_type(t, globalns, localns)
    268     """
    269     if isinstance(t, ForwardRef):
--> 270         return t._evaluate(globalns, localns)
    271     if isinstance(t, _GenericAlias):
    272         ev_args = tuple(_eval_type(a, globalns, localns) for a in t.__args__)

~/.conda/envs/trusted_analytics/lib/python3.8/typing.py in _evaluate(self, globalns, localns)
    516                 localns = globalns
    517             self.__forward_value__ = _type_check(
--> 518                 eval(self.__forward_code__, globalns, localns),
    519                 "Forward references must evaluate to types.",
    520                 is_argument=self.__forward_is_argument__)

~/software/adversarial-robustness-toolbox/art/estimators/classification/pytorch.py in <module>

NameError: name 'torch' is not defined
```

No issues when trying to load a `PyTorchClassifier` normally.


Discussion:
I'll try to take a look at this over the weekend, but my suspicion is that they are using a bad forward reference in their type hint.

We should be robust to such foibles – should wrap our attempt to get types in a try-except.
This is due to the fact that they are using forward references to torch objects (so that they can use those objects in their type hints without actually having to use torch:

```python
from typing import get_type_hints

def f(x: "torch.optim.Optimizer"):
    return

get_type_hints(f)  # NameError
```

Resolving patch:
diff --git a/src/hydra_zen/structured_configs/_implementations.py b/src/hydra_zen/structured_configs/_implementations.py
--- a/src/hydra_zen/structured_configs/_implementations.py
+++ b/src/hydra_zen/structured_configs/_implementations.py
@@ -647,8 +647,9 @@ def builds(
             type_hints = get_type_hints(target.__init__)
         else:
             type_hints = get_type_hints(target)
-    except TypeError:
-        # Covers case for ufuncs, which do not have inspectable type hints
+    except (TypeError, NameError):
+        # TypeError: Covers case for ufuncs, which do not have inspectable type hints
+        # NameError: Covers case for unresolved forward reference
         type_hints = defaultdict(lambda: Any)
 
     sig_by_kind: Dict[Any, List[inspect.Parameter]] = {


Test patch:
diff --git a/tests/test_signature_parsing.py b/tests/test_signature_parsing.py
--- a/tests/test_signature_parsing.py
+++ b/tests/test_signature_parsing.py
@@ -258,3 +258,18 @@ def test_builds_partial_with_full_sig_excludes_non_specified_params(
 
     assert isinstance(Conf.a_function, Just) and "func" in Conf.a_function.path
     assert isinstance(Conf.a_class, Just) and "dict" in Conf.a_class.path
+
+
+def f_with_fwd_ref(x: "torch.optim.Optimizer"):  # noqa: F821
+    return
+
+
+class A_w_fwd_ref:
+    def __init__(self, x: "torch.optim.Optimizer"):  # noqa: F821
+        pass
+
+
+@pytest.mark.parametrize("obj", [f_with_fwd_ref, A_w_fwd_ref])
+def test_sig_with_unresolved_fwd_ref(obj):
+    # builds should gracefully skip signature parsing for unresolved fwd-references
+    instantiate(builds(obj, x=1))