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

Issue:
Run pyright as part of CI to verify static annotations
Should be able to validate:

```python
class A:
    x = 11
    def __init__(self) -> None:
        pass

def f(x: int) -> int: return x

conf_a_partial = builds(A, hydra_partial=True)
partial_out = instantiate(conf_a_partial)
should_be_A = partial_out()

conf_f_partial = builds(f, hydra_partial=True)
partial_out_f = instantiate(conf_f_partial)
should_be_f = partial_out_f()


conf_A = builds(A)
should_be_A_again = instantiate(conf_A)
a = should_be_A()
a


def f(x: int) -> int: return x

conf_f = builds(f)
should_be_int = instantiate(conf_f)


should_be_int


conf_just_f = just(f)
conf_just_f
oo = instantiate(conf_just_f)
oo
```


Discussion:

Resolving patch:
diff --git a/src/hydra_utils/_hydra_overloads.py b/src/hydra_utils/_hydra_overloads.py
--- a/src/hydra_utils/_hydra_overloads.py
+++ b/src/hydra_utils/_hydra_overloads.py
@@ -24,6 +24,8 @@
 
 T = TypeVar("T")
 
+Callable_T = TypeVar("Callable_T", bound=Callable)
+
 
 @overload
 def instantiate(config: Just[T], *args: Any, **kwargs: Any) -> T:  # pragma: no cover
@@ -32,11 +34,18 @@ def instantiate(config: Just[T], *args: Any, **kwargs: Any) -> T:  # pragma: no
 
 @overload
 def instantiate(
-    config: PartialBuilds[T], *args: Any, **kwargs: Any
+    config: PartialBuilds[Type[T]], *args: Any, **kwargs: Any
 ) -> Partial[T]:  # pragma: no cover
     ...
 
 
+@overload
+def instantiate(
+    config: PartialBuilds[Callable_T], *args: Any, **kwargs: Any
+) -> Partial[Callable_T]:  # pragma: no cover
+    ...
+
+
 @overload
 def instantiate(
     config: Builds[Type[T]], *args: Any, **kwargs: Any
diff --git a/src/hydra_utils/structured_configs/_implementations.py b/src/hydra_utils/structured_configs/_implementations.py
--- a/src/hydra_utils/structured_configs/_implementations.py
+++ b/src/hydra_utils/structured_configs/_implementations.py
@@ -138,7 +138,7 @@ class hydrated_dataclass:
 
     def __init__(
         self,
-        target: Importable,
+        target: Any,
         *,
         populate_full_signature: bool = False,
         hydra_partial: bool = False,
@@ -177,10 +177,10 @@ def __init__(
         self._target = target
         self._populate_full_signature = populate_full_signature
         self._hydra_recursive = hydra_recursive
-        self._hydra_convert = hydra_convert
+        self._hydra_convert: Literal["none", "partial", "all"] = hydra_convert
         self._hydra_partial = hydra_partial
 
-    def __call__(self, decorated_obj: type) -> Builds[Importable]:
+    def __call__(self, decorated_obj: Any) -> Builds:
         if not isinstance(decorated_obj, type):
             raise NotImplementedError(
                 "Class instances are not supported by `hydrated_dataclass` (yet)."
diff --git a/src/hydra_utils/typing.py b/src/hydra_utils/typing.py
--- a/src/hydra_utils/typing.py
+++ b/src/hydra_utils/typing.py
@@ -14,9 +14,7 @@
 ]
 
 
-# TODO: Define Instantiable
-
-_T = TypeVar("_T")
+_T = TypeVar("_T", covariant=True)
 
 
 class Partial(Generic[_T]):
@@ -64,7 +62,7 @@ class Instantiable(DataClass, Protocol[_T]):  # pragma: no cover
 
 class Just(Instantiable, Protocol[_T]):
     obj: str  # interpolated string for importing obj
-    _target_: str = "hydra_utils.identity"  # TODO: update this
+    _target_: str = "hydra_utils.funcs.identity"  # TODO: update this
 
 
 class Builds(Instantiable, Protocol[_T]):  # pragma: no cover


Test patch:
diff --git a/tests/annotations/behaviors.py b/tests/annotations/behaviors.py
new file mode 100644
--- /dev/null
+++ b/tests/annotations/behaviors.py
@@ -0,0 +1,50 @@
+from typing import Tuple
+
+from hydra_utils import builds, instantiate, just
+
+
+class A:
+    def __init__(self) -> None:
+        self.x: Tuple[int, str] = (1, "hi")
+
+
+def f(x: int) -> int:
+    return x
+
+
+def requires_A(x: int):
+    pass
+
+
+# test type behaviors
+
+# test builds(..., hydra_partial=True)
+conf_a_partial = builds(A, hydra_partial=True)
+partial_out = instantiate(conf_a_partial)
+should_be_a = partial_out()
+out: Tuple[int, str] = should_be_a.x
+
+
+conf_f_partial = builds(f, hydra_partial=True)
+partial_out_f = instantiate(conf_f_partial)
+should_be_f = partial_out_f()
+should_be_int_output_of_f: int = should_be_f(2)
+
+# test builds(..., hydra_partial=False)
+conf_A = builds(A)
+should_be_a_again = instantiate(conf_A)
+out2: Tuple[int, str] = should_be_a_again.x
+
+conf_f = builds(f)
+should_be_int: int = instantiate(conf_f)
+
+
+# test just(...)
+conf_just_f = just(f)
+just_f = instantiate(conf_just_f)
+yet_another_int: int = just_f(10)
+
+conf_just_A = just(A)
+just_A = instantiate(conf_just_A)
+instance_of_a = A()
+out3: Tuple[int, str] = instance_of_a.x
diff --git a/tests/annotations/declarations.py b/tests/annotations/declarations.py
new file mode 100644
--- /dev/null
+++ b/tests/annotations/declarations.py
@@ -0,0 +1,49 @@
+from typing import Callable, Tuple, Type
+
+from hydra_utils import builds, instantiate, just
+from hydra_utils.typing import Builds, Just, Partial, PartialBuilds
+
+
+class A:
+    def __init__(self) -> None:
+        self.x: Tuple[int, str] = (1, "hi")
+
+
+def f(x: int) -> int:
+    return x
+
+
+def requires_A(x: int):
+    pass
+
+
+# test type behaviors
+
+# test builds(..., hydra_partial=True)
+conf_a_partial = builds(
+    A, hydra_partial=True
+)  # there is something really weird where annotating : PartialBuilds[Type[A]] breaks this..
+partial_out: Partial[A] = instantiate(conf_a_partial)
+out_a: A = partial_out()
+
+f_sig = Callable[[int], int]
+conf_f_partial: PartialBuilds[f_sig] = builds(f, hydra_partial=True)
+partial_out_f: Partial[f_sig] = instantiate(conf_f_partial)
+
+# test builds(..., hydra_partial=False)
+conf_A: Builds[Type[A]] = builds(A, hydra_partial=False)
+should_be_a_again: A = instantiate(conf_A)
+
+conf_f: Builds[f_sig] = builds(f, hydra_partial=False)
+should_be_int: int = instantiate(conf_f)
+
+# test builds(...)
+conf_A: Builds[Type[A]] = builds(A)
+should_be_a_again: A = instantiate(conf_A)
+
+conf_f: Builds[f_sig] = builds(f)
+should_be_int: int = instantiate(conf_f)
+
+# test just(...)
+conf_just_f: Just[f_sig] = just(f)
+conf_just_A: Just[Type[A]] = just(A)