Repository: facebookresearch/hydra

Issue:
[Feature Request] Allow @hydra.main() to take a config object and pass it through
# 🚀 Feature Request

Allow @hydra.main() to take a config and pass it through


Discussion:
Will look into it.
Hi, is this feature the only way to run test of a decorated hydra function?
I have something like this with `run` decorated by `hydra.main`:
```
def test_main():
        experimental.initialize("../cfg/")
        cfg = experimental.compose("../cfg/config.yaml")
        run(cfg)
```
but it is not allowed.
currently there is no supported way to do it. this feature is exactly about supporting this use case.

you can work around this easily though:

```python
def real_run(cfg):
  ...

@hydra.main(...)
def run(cfg):
  real_run(cfg)

def test_main():
        experimental.initialize("../cfg/")
        cfg = experimental.compose("../cfg/config.yaml")
        real_run(cfg)
```

Patch:
diff --git a/hydra/main.py b/hydra/main.py
--- a/hydra/main.py
+++ b/hydra/main.py
@@ -1,6 +1,8 @@
 # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
 import functools
-from typing import Callable, Optional
+from typing import Any, Callable, Optional
+
+from omegaconf import DictConfig
 
 from ._internal.utils import get_args_parser, run_hydra
 from .types import TaskFunction
@@ -10,7 +12,7 @@ def main(
     config_path: Optional[str] = None,
     config_name: Optional[str] = None,
     strict: Optional[bool] = None,
-) -> Callable[[TaskFunction], Callable[[], None]]:
+) -> Callable[[TaskFunction], Any]:
     """
     :param config_path: the config path, a directory relative to the declaring python file.
     :param config_name: the name of the config (usually the file name without the .yaml extension)
@@ -20,14 +22,20 @@ def main(
 
     def main_decorator(task_function: TaskFunction) -> Callable[[], None]:
         @functools.wraps(task_function)
-        def decorated_main() -> None:
-            run_hydra(
-                args_parser=get_args_parser(),
-                task_function=task_function,
-                config_path=config_path,
-                config_name=config_name,
-                strict=strict,
-            )
+        def decorated_main(cfg_passthrough: Optional[DictConfig] = None) -> Any:
+            if cfg_passthrough is not None:
+                return task_function(cfg_passthrough)
+            else:
+                args = get_args_parser()
+                # no return value from run_hydra() as it may sometime actually run the task_function
+                # multiple times (--multirun)
+                run_hydra(
+                    args_parser=args,
+                    task_function=task_function,
+                    config_path=config_path,
+                    config_name=config_name,
+                    strict=strict,
+                )
 
         return decorated_main
 


Test patch:
diff --git a/tests/test_apps/app_with_cfg_groups/my_app.py b/tests/test_apps/app_with_cfg_groups/my_app.py
--- a/tests/test_apps/app_with_cfg_groups/my_app.py
+++ b/tests/test_apps/app_with_cfg_groups/my_app.py
@@ -1,12 +1,14 @@
 # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
+from typing import Any
+
 from omegaconf import DictConfig
 
 import hydra
 
 
 @hydra.main(config_path="conf", config_name="config")
-def my_app(_: DictConfig) -> None:
-    pass
+def my_app(cfg: DictConfig) -> Any:
+    return cfg
 
 
 if __name__ == "__main__":
diff --git a/tests/test_compose.py b/tests/test_compose.py
--- a/tests/test_compose.py
+++ b/tests/test_compose.py
@@ -250,3 +250,13 @@ def test_initialize_with_module(restore_singletons: Any) -> None:
     assert compose(config_name="config") == {
         "optimizer": {"type": "nesterov", "lr": 0.001}
     }
+
+
+def test_hydra_main_passthrough(restore_singletons: Any) -> None:
+    initialize_with_file(
+        calling_file="tests/test_apps/app_with_cfg_groups/my_app.py", config_path="conf"
+    )
+    from tests.test_apps.app_with_cfg_groups.my_app import my_app
+
+    cfg = compose(config_name="config", overrides=["optimizer.lr=0.1"])
+    assert my_app(cfg) == {"optimizer": {"type": "nesterov", "lr": 0.1}}