Repository: facebookresearch/hydra

Issue:
[Feature Request] Allow Initialize to take a module
# 🚀 Feature Request

Allow hydra.experimental.initialize to take a calling module


Discussion:

Resolving patch:
diff --git a/examples/advanced/ad_hoc_composition/hydra_compose_example.py b/examples/advanced/ad_hoc_composition/hydra_compose_example.py
--- a/examples/advanced/ad_hoc_composition/hydra_compose_example.py
+++ b/examples/advanced/ad_hoc_composition/hydra_compose_example.py
@@ -4,7 +4,7 @@
 if __name__ == "__main__":
     # initialize the Hydra subsystem.
     # This is needed for apps that cannot have a standard @hydra.main() entry point
-    initialize(config_dir="conf", strict=True)
+    initialize(config_path="conf", strict=True)
 
     cfg = compose("config.yaml", overrides=["db=mysql", "db.user=${env:USER}"])
     print(cfg.pretty(resolve=True))
diff --git a/hydra/experimental/compose.py b/hydra/experimental/compose.py
--- a/hydra/experimental/compose.py
+++ b/hydra/experimental/compose.py
@@ -9,31 +9,58 @@
 
 
 def initialize(
-    config_dir: Optional[str] = None,
+    config_path: Optional[str] = None,
     strict: Optional[bool] = None,
     caller_stack_depth: int = 1,
 ) -> None:
     """
-    :param config_dir: config directory relative to the calling script
-    :param strict:
-    :param caller_stack_depth:
-    :return:
+    Initialize automatically detect the calling file or module.
+    config_path is relative to the detected calling for or module.
+
+    :param config_path: A directory relative to the declaring python file or module
+    :param strict: (Deprecated), will be removed in the next major version
+    :param caller_stack_depth: stack depth of module the config_path is relative to
     """
     calling_file, calling_module = detect_calling_file_or_module_from_stack_frame(
         caller_stack_depth + 1
     )
     Hydra.create_main_hydra_file_or_module(
-        calling_file, calling_module, config_dir, strict
+        calling_file, calling_module, config_path, strict
     )
 
 
+def initialize_with_file(
+    calling_file: Optional[str], config_path: Optional[str] = None
+) -> None:
+    """
+    Initialize Hydra and add the config_path to the search path.
+    The config path is relative to the calling_file.
+    :param calling_file : The file to make the config_path relative to
+    :param config_path : The config path
+    """
+    Hydra.create_main_hydra_file_or_module(calling_file, None, config_path, None)
+
+
+def initialize_with_module(
+    calling_module: Optional[str], config_path: Optional[str] = None
+) -> None:
+    """
+    Initialize Hydra and add the config_path to the search path.
+    The config path is relative to the calling_module.
+    :param calling_module : The module to make the config_path relative to
+    :param config_path : The config path
+    """
+
+    Hydra.create_main_hydra_file_or_module(None, calling_module, config_path, None)
+
+
 def compose(
     config_name: Optional[str] = None,
     overrides: List[str] = [],
     strict: Optional[bool] = None,
 ) -> DictConfig:
     """
-    :param config_name: optional config name to load
+    :param config_name: the name of the config (usually the file name without the .yaml extension)
     :param overrides: list of overrides for config file
     :param strict: optionally override the default strict mode
     :return: the composed config


Test patch:
diff --git a/hydra/test_utils/test_utils.py b/hydra/test_utils/test_utils.py
--- a/hydra/test_utils/test_utils.py
+++ b/hydra/test_utils/test_utils.py
@@ -42,7 +42,7 @@ def __init__(self) -> None:
 
     def __enter__(self) -> "GlobalHydraContext":
         hydra.experimental.initialize(
-            config_dir=self.config_dir, strict=self.strict, caller_stack_depth=2
+            config_path=self.config_dir, strict=self.strict, caller_stack_depth=2
         )
         return self
 
diff --git a/tests/test_compose.py b/tests/test_compose.py
--- a/tests/test_compose.py
+++ b/tests/test_compose.py
@@ -9,6 +9,7 @@
 from hydra.core.global_hydra import GlobalHydra
 from hydra.errors import HydraException
 from hydra.experimental import compose, initialize
+from hydra.experimental.compose import initialize_with_file, initialize_with_module
 from hydra.test_utils.test_utils import (
     TGlobalHydraContext,
     chdir_hydra_root,
@@ -20,13 +21,13 @@
 
 def test_initialize(restore_singletons: Any) -> None:
     assert not GlobalHydra().is_initialized()
-    initialize(config_dir=None)
+    initialize(config_path=None)
     assert GlobalHydra().is_initialized()
 
 
 def test_initialize_with_config_dir(restore_singletons: Any) -> None:
     assert not GlobalHydra().is_initialized()
-    initialize(config_dir="../hydra/test_utils/configs")
+    initialize(config_path="../hydra/test_utils/configs")
     assert GlobalHydra().is_initialized()
 
     gh = GlobalHydra.instance()
@@ -130,7 +131,7 @@ def test_strict_deprecation_warning(restore_singletons: Any) -> None:
         "\nSee https://hydra.cc/next/upgrades/0.11_to_1.0/strict_mode_flag_deprecated"
     )
     with pytest.warns(expected_warning=UserWarning, match=re.escape(msg)):
-        initialize(config_dir=None, strict=True)
+        initialize(config_path=None, strict=True)
 
 
 @pytest.mark.usefixtures("restore_singletons")
@@ -231,3 +232,21 @@ def test_missing_init_py_error(hydra_global_context: TGlobalHydraContext) -> Non
             config_dir="../hydra/test_utils/configs/missing_init_py"
         ):
             ...
+
+
+def test_initialize_with_file(restore_singletons: Any) -> None:
+    initialize_with_file(
+        calling_file="tests/test_apps/app_with_cfg_groups/my_app.py", config_path="conf"
+    )
+    assert compose(config_name="config") == {
+        "optimizer": {"type": "nesterov", "lr": 0.001}
+    }
+
+
+def test_initialize_with_module(restore_singletons: Any) -> None:
+    initialize_with_module(
+        calling_module="tests.test_apps.app_with_cfg_groups.my_app", config_path="conf"
+    )
+    assert compose(config_name="config") == {
+        "optimizer": {"type": "nesterov", "lr": 0.001}
+    }