Repository: facebookresearch/hydra

Issue:
[Bug] Instantiating objects and calling methods and functions CRASHES
# 🐛 Bug

https://hydra.cc/docs/next/patterns/objects#instantiating-objects-and-calling-methods-and-functions
Following the 1.0rc instructions for object instantiation results in "ValueError: Input config does not have a cls field", followed by "UnboundLocalError: local variable 'cls' referenced before assignment". 

<!-- A clear and concise description of what the bug is. -->

## To reproduce
** Minimal Code/Config snippet to reproduce **
`pip install hydra-core --upgrade --pre`

Basically copy pasted from the docs. All you have to do is make these files in the same dir, then run `python main.py`.

```yaml
# config.yaml
myobject:
  target: models.Foo
  params:
    x: 10
    y: 20
    
myclassmethod:
  target: models.Foo.class_method
  params:
    z: 5

mystaticmethod:
  target: models.Foo.static_method
  params:
    z: 15

myfunction:
  target: models.bar
  params:
    z: 15
```
```python
# models.py
from typing import Any
class Foo:
  def __init__(self, x: int, y: int) -> None:
    self.x = x
    self.y = y

  @classmethod
  def class_method(self, z: int) -> Any:
    return self(z, 10)

  @staticmethod
  def static_method(z: int) -> int:
    return z + 1

def bar(z: int) -> int:
  return z + 2
```
```python
# main.py
import hydra
from models import Foo

@hydra.main(config_name="config.yaml")
def app(cfg):
  foo1: Foo = hydra.utils.call(cfg.myobject)  # Foo(10, 20)
  print(Foo)
  foo2: Foo = hydra.utils.call(cfg.myclassmethod)  # Foo(5, 10)
  print(Foo)
  ret1: int = hydra.utils.call(cfg.mystaticmethod)  # 16
  print(ret1)
  ret2: int = hydra.utils.call(cfg.myfunction)  # 17
  print(ret2)

if __name__ == "__main__":
    app()
```


** Stack trace/error message **
```
Traceback (most recent call last):
  File "/home/andrew/.miniconda3/envs/pt/lib/python3.7/site-packages/hydra/utils.py", line 29, in call
    cls = _get_cls_name(config)
  File "/home/andrew/.miniconda3/envs/pt/lib/python3.7/site-packages/hydra/_internal/utils.py", line 489, in _get_cls_name
    raise ValueError("Input config does not have a cls field")
ValueError: Input config does not have a cls field

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "main.py", line 16, in <module>
    app()
  File "/home/andrew/.miniconda3/envs/pt/lib/python3.7/site-packages/hydra/main.py", line 37, in decorated_main
    strict=strict,
  File "/home/andrew/.miniconda3/envs/pt/lib/python3.7/site-packages/hydra/_internal/utils.py", line 253, in run_hydra
    lambda: hydra.run(
  File "/home/andrew/.miniconda3/envs/pt/lib/python3.7/site-packages/hydra/_internal/utils.py", line 185, in run_and_report
    func()
  File "/home/andrew/.miniconda3/envs/pt/lib/python3.7/site-packages/hydra/_internal/utils.py", line 256, in <lambda>
    overrides=args.overrides,
  File "/home/andrew/.miniconda3/envs/pt/lib/python3.7/site-packages/hydra/_internal/hydra.py", line 114, in run
    job_subdir_key=None,
  File "/home/andrew/.miniconda3/envs/pt/lib/python3.7/site-packages/hydra/core/utils.py", line 107, in run_job
    ret.return_value = task_function(task_cfg)
  File "main.py", line 6, in app
    foo1: Foo = hydra.utils.call(cfg.myobject)  # Foo(10, 20)
  File "/home/andrew/.miniconda3/envs/pt/lib/python3.7/site-packages/hydra/utils.py", line 37, in call
    log.error(f"Error instantiating '{cls}' : {e}")
UnboundLocalError: local variable 'cls' referenced before assignment
```

## Expected Behavior
<!-- A clear and concise description of what you expected to happen. -->
The code in the docs for Hydra's class instantiation should work.

## System information
- **Hydra Version** :  1.0rc
- **Python version** : 3.7
- **Virtual environment type and version** :  None
- **Operating system** :  Manjaro Linux

## Additional context
Add any other context about the problem here.



Discussion:
The instructions on the rc are for master, unfortunately there is no official docs version for 1.0.0rc1 and there have been some changes.

try with cls.
This is going to change again soon (see #737).
Okay.
Replacing "target" with "cls" does not work, same error.
Please provide minimal repro instructions, including how you are installing.
@shagunsodhani, can you help with this one?
@omry okay, I updated with the `pip install hydra-core --upgrade --pre` command I used.
I could run the code by replacing `target` by `cls` in the config (as shown):

```
#config.yaml
myobject:
  cls: models.Foo
  params:
    x: 10
    y: 20

myclassmethod:
  cls: models.Foo.class_method
  params:
    z: 5

mystaticmethod:
  cls: models.Foo.static_method
  params:
    z: 15

myfunction:
  cls: models.bar
  params:
    z: 15
```

Could you please try with this? 

#737 fixes this error (and introduces some changes)
Huh, it works. My apologies, I must have tested incorrectly when I tried replacing `target` with `cls` the first time. Happy there's a solution now. Thanks guys!
thanks @andrewjong.

@shagunsodhani, can you try with a badly spelled cls to see that the error makes sense?
Yup. As misspelled `cls` will cause the problem. The right thing is to catch the `ValueError: Input config does not have a cls field` and  pass it up. Will fix it with #737 
Pass it up as in a HydraException.

Resolving patch:
diff --git a/hydra/_internal/utils.py b/hydra/_internal/utils.py
--- a/hydra/_internal/utils.py
+++ b/hydra/_internal/utils.py
@@ -558,4 +558,4 @@ def _has_field(field: str) -> bool:
     elif _has_field(field="class"):
         return _getcls(field="class")
     else:
-        raise ValueError("Input config does not have a target field")
+        raise HydraException("Input config does not have a `target` field")
diff --git a/hydra/utils.py b/hydra/utils.py
--- a/hydra/utils.py
+++ b/hydra/utils.py
@@ -4,7 +4,7 @@
 from pathlib import Path
 from typing import Any, Callable, Union
 
-from omegaconf import DictConfig
+from omegaconf import DictConfig, OmegaConf
 
 from hydra._internal.utils import (
     _call_callable,
@@ -26,6 +26,8 @@ def call(config: Union[ObjectConf, DictConfig], *args: Any, **kwargs: Any) -> An
     :param kwargs: optional named parameters pass-through
     :return: the return value from the specified class or method
     """
+    if OmegaConf.is_none(config):
+        return None
     try:
         cls = _get_cls_name(config)
         type_or_callable = _locate(cls)
@@ -35,7 +37,7 @@ def call(config: Union[ObjectConf, DictConfig], *args: Any, **kwargs: Any) -> An
             assert callable(type_or_callable)
             return _call_callable(type_or_callable, config, *args, **kwargs)
     except Exception as e:
-        raise HydraException(f"Error instantiating '{cls}' : {e}") from e
+        raise HydraException(f"Error calling '{cls}' : {e}") from e
 
 
 # Alias for call


Test patch:
diff --git a/tests/test_internal_utils.py b/tests/test_internal_utils.py
--- a/tests/test_internal_utils.py
+++ b/tests/test_internal_utils.py
@@ -55,6 +55,19 @@ def test_get_column_widths(matrix: Any, expected: Any) -> None:
             "class",
             id="DictConfig:class_target",
         ),
+        # check that `target` is prioritized over `cls`/`class`.
+        pytest.param(
+            OmegaConf.create({"cls": "foo", "target": "bar"}),
+            "bar",
+            "cls",
+            id="DictConfig:cls_target",
+        ),
+        pytest.param(
+            OmegaConf.create({"class": "foo", "target": "bar"}),
+            "bar",
+            "class",
+            id="DictConfig:class_target",
+        ),
     ],
 )
 def test_get_class_name(
diff --git a/tests/test_utils.py b/tests/test_utils.py
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -109,6 +109,8 @@ def test_get_class(path: str, expected_type: type) -> None:
             "43",
             id="builtin_types",
         ),
+        # Check that none is instantiated correctly
+        pytest.param(None, {}, None, id="instantiate_none",),
         # passthrough
         pytest.param(
             {"target": "tests.AClass"},