Skip to content

Commit

Permalink
add --info flag
Browse files Browse the repository at this point in the history
  • Loading branch information
omry committed Jun 10, 2020
1 parent 9db71bf commit 953d155
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 74 deletions.
10 changes: 5 additions & 5 deletions hydra/_internal/config_loader_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,11 @@ def load_configuration(
parsed_overrides = [self._parse_override(override) for override in overrides]

if config_name is not None and not self.repository.config_exists(config_name):
# TODO: handle schema as a special case
descs = [
f"\t{src.path} (from {src.provider})"
for src in self.repository.get_sources()
]
descs = []
for src in self.repository.get_sources():
if src.provider != "schema":
descs.append(f"\t{repr(src)}")

lines = "\n".join(descs)
raise MissingConfigException(
missing_cfg_file=config_name,
Expand Down
109 changes: 80 additions & 29 deletions hydra/_internal/hydra.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from omegaconf import DictConfig, OmegaConf, open_dict

from hydra._internal.utils import get_column_widths
from hydra._internal.utils import get_column_widths, run_and_report
from hydra.core.config_loader import ConfigLoader
from hydra.core.config_search_path import ConfigSearchPath
from hydra.core.hydra_config import HydraConfig
Expand All @@ -21,6 +21,7 @@
configure_log,
run_job,
setup_globals,
simple_stdout_log_config,
)
from hydra.errors import MissingConfigException
from hydra.plugins.completion_plugin import CompletionPlugin
Expand Down Expand Up @@ -85,16 +86,6 @@ def __init__(self, task_name: str, config_loader: ConfigLoader) -> None:
"""
setup_globals()
self.config_loader = config_loader

for source in config_loader.get_sources():
# if specified, make sure main config search path exists
if source.provider == "main":
if not source.exists(""):
raise MissingConfigException(
missing_cfg_file=source.path,
message=f"Primary config dir not found: {source}",
)

JobRuntime().set("name", task_name)

def run(
Expand Down Expand Up @@ -146,18 +137,35 @@ def get_sanitized_hydra_cfg(src_cfg: DictConfig) -> DictConfig:
del cfg.hydra["help"]
return cfg

def show_cfg(
self, config_name: Optional[str], overrides: List[str], cfg_type: str
) -> None:
def _get_cfg(
self,
config_name: Optional[str],
overrides: List[str],
cfg_type: str,
with_log_configuration: bool,
) -> DictConfig:
assert cfg_type in ["job", "hydra", "all"]
cfg = self.compose_config(
config_name=config_name, overrides=overrides, with_log_configuration=True
config_name=config_name,
overrides=overrides,
with_log_configuration=with_log_configuration,
)
if cfg_type == "job":
with open_dict(cfg):
del cfg["hydra"]
elif cfg_type == "hydra":
cfg = self.get_sanitized_hydra_cfg(cfg)
return cfg

def show_cfg(
self, config_name: Optional[str], overrides: List[str], cfg_type: str
) -> None:
cfg = self._get_cfg(
config_name=config_name,
overrides=overrides,
cfg_type=cfg_type,
with_log_configuration=False,
)
print(cfg.pretty())

@staticmethod
Expand Down Expand Up @@ -302,6 +310,11 @@ def _log_header(header: str, prefix: str = "", filler: str = "-") -> None:
log.debug(prefix + header)
log.debug(prefix + "".ljust(len(header), filler))

@staticmethod
def _log_footer(header: str, prefix: str = "", filler: str = "-") -> None:
assert log is not None
log.debug(prefix + "".ljust(len(header), filler))

def _print_plugins(self) -> None:
assert log is not None
self._log_header(header="Installed Hydra Plugins", filler="*")
Expand Down Expand Up @@ -337,11 +350,11 @@ def _print_search_path(self) -> None:
box.append([sp.provider, sp.full_path()])

provider_pad, search_path_pad = get_column_widths(box)
header = "| {} | {} |".format(
"Provider".ljust(provider_pad), "Search path".ljust(search_path_pad)
)
self._log_header(
"| {} | {} |".format(
"Provider".ljust(provider_pad), "Search path".ljust(search_path_pad)
),
filler="-",
header=header, filler="-",
)

for source in self.config_loader.get_sources():
Expand All @@ -351,6 +364,7 @@ def _print_search_path(self) -> None:
source.full_path().ljust(search_path_pad),
)
)
self._log_footer(header=header, filler="-")

def _print_composition_trace(self) -> None:
# Print configurations used to compose the config object
Expand All @@ -372,15 +386,13 @@ def _print_composition_trace(self) -> None:
padding = get_column_widths(box)
del box[0]

self._log_header(
"| {} | {} | {} | {} |".format(
"Config name".ljust(padding[0]),
"Search path".ljust(padding[1]),
"Provider".ljust(padding[2]),
"Schema provider".ljust(padding[3]),
),
filler="-",
header = "| {} | {} | {} | {} |".format(
"Config name".ljust(padding[0]),
"Search path".ljust(padding[1]),
"Provider".ljust(padding[2]),
"Schema provider".ljust(padding[3]),
)
self._log_header(header=header, filler="-")

for row in box:
log.debug(
Expand All @@ -392,6 +404,8 @@ def _print_composition_trace(self) -> None:
)
)

self._log_footer(header=header, filler="-")

def _print_plugins_profiling_info(self, top_n: int) -> None:
assert log is not None
stats = Plugins.instance().get_stats()
Expand All @@ -417,9 +431,9 @@ def _print_plugins_profiling_info(self, top_n: int) -> None:
filler="-",
)

header = f"| {box[0][0].ljust(padding[0])} | {box[0][1].ljust(padding[1])} |"
self._log_header(
f"| {box[0][0].ljust(padding[0])} | {box[0][1].ljust(padding[1])} |",
filler="-",
header=header, filler="-",
)
del box[0]

Expand All @@ -428,6 +442,8 @@ def _print_plugins_profiling_info(self, top_n: int) -> None:
b = row[1].ljust(padding[1])
log.debug(f"| {a} | {b} |")

self._log_footer(header=header, filler="-")

def _print_debug_info(self) -> None:
assert log is not None
if log.isEnabledFor(logging.DEBUG):
Expand All @@ -452,6 +468,16 @@ def compose_config(
otherwise forces specific behavior.
:return:
"""

for source in self.config_loader.get_sources():
# if specified, make sure main config search path exists
if source.provider == "main":
if not source.exists(""):
raise MissingConfigException(
missing_cfg_file=source.path,
message=f"Primary config dir not found: {source}",
)

cfg = self.config_loader.load_configuration(
config_name=config_name, overrides=overrides, strict=strict
)
Expand All @@ -466,3 +492,28 @@ def compose_config(
log = logging.getLogger(__name__)
self._print_debug_info()
return cfg

def show_info(self, config_name: Optional[str], overrides: List[str]) -> None:
from .. import __version__

simple_stdout_log_config(level=logging.DEBUG)
global log
log = logging.getLogger(__name__)
self._log_header(f"Hydra {__version__}", filler="=")
self._print_plugins()
self._print_search_path()
self._print_plugins_profiling_info(top_n=10)

cfg = run_and_report(
lambda: self._get_cfg(
config_name=config_name,
overrides=overrides,
cfg_type="job",
with_log_configuration=False,
)
)
self._print_composition_trace()

log.debug("\n")
self._log_header(header="Config", filler="*")
log.debug(cfg.pretty())
23 changes: 17 additions & 6 deletions hydra/_internal/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,9 @@ def create_config_search_path(search_path_dir: Optional[str]) -> ConfigSearchPat
return search_path


def run_and_report(func: Any) -> None:
def run_and_report(func: Any) -> Any:
try:
func()
return func()
except (HydraException, OmegaConfBaseException) as ex:
if "HYDRA_FULL_ERROR" in os.environ and os.environ["HYDRA_FULL_ERROR"] == "1":
print_exc()
Expand Down Expand Up @@ -231,9 +231,12 @@ def run_hydra(
calling_file, calling_module, config_dir
)

hydra = Hydra.create_main_hydra2(
task_name=task_name, config_search_path=search_path, strict=strict
hydra = run_and_report(
lambda: Hydra.create_main_hydra2(
task_name=task_name, config_search_path=search_path, strict=strict
)
)

try:
if args.help:
hydra.app_help(config_name=config_name, args_parser=args_parser, args=args)
Expand All @@ -245,10 +248,12 @@ def run_hydra(
sys.exit(0)

has_show_cfg = args.cfg is not None
num_commands = args.run + has_show_cfg + args.multirun + args.shell_completion
num_commands = (
args.run + has_show_cfg + args.multirun + args.shell_completion + args.info
)
if num_commands > 1:
raise ValueError(
"Only one of --run, --multirun, -cfg and --shell_completion can be specified"
"Only one of --run, --multirun, -cfg, --info and --shell_completion can be specified"
)
if num_commands == 0:
args.run = True
Expand Down Expand Up @@ -280,6 +285,8 @@ def run_hydra(
config_name=config_name, overrides=args.overrides
)
)
elif args.info:
hydra.show_info(config_name=config_name, overrides=args.overrides)
else:
sys.stderr.write("Command not specified\n")
sys.exit(1)
Expand Down Expand Up @@ -363,6 +370,10 @@ def get_args_parser() -> argparse.ArgumentParser:
"-cn",
help="Overrides the config_name specified in hydra.main()",
)

parser.add_argument(
"--info", "-i", action="store_true", help="Print Hydra information",
)
return parser


Expand Down
9 changes: 9 additions & 0 deletions hydra/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@
log = logging.getLogger(__name__)


def simple_stdout_log_config(level: int = logging.INFO) -> None:
root = logging.getLogger()
root.setLevel(level)
handler = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter("%(message)s")
handler.setFormatter(formatter)
root.addHandler(handler)


def configure_log(
log_config: DictConfig, verbose_config: Union[bool, str, Sequence[str]]
) -> None:
Expand Down
1 change: 1 addition & 0 deletions news/662.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a new --info flag to show Hydra debug information without running the user function.
8 changes: 6 additions & 2 deletions tests/test_compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,9 @@ def test_compose(
assert ret == expected


def test_missing_init_py_error(hydra_global_context: TGlobalHydraContext) -> None:
def test_missing_init_py_error(
restore_singletons: Any, hydra_global_context: TGlobalHydraContext
) -> None:
with pytest.raises(
Exception,
match=re.escape(
Expand All @@ -235,7 +237,9 @@ def test_missing_init_py_error(hydra_global_context: TGlobalHydraContext) -> Non
with hydra_global_context(
config_dir="../hydra/test_utils/configs/missing_init_py"
):
...
hydra = GlobalHydra.instance().hydra
assert hydra is not None
hydra.compose_config(config_name=None, overrides=[])


def test_initialize_with_file(restore_singletons: Any) -> None:
Expand Down
2 changes: 1 addition & 1 deletion tests/test_core_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from hydra.core.hydra_config import HydraConfig


def test_foo(restore_singletons: Any) -> Any:
def test_accessing_hydra_config(restore_singletons: Any) -> Any:
utils.setup_globals()

config_loader = ConfigLoaderImpl(
Expand Down
2 changes: 2 additions & 0 deletions tests/test_hydra.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,7 @@ def test_sweep_complex_defaults(
--config_path,-cp : Overrides the config_path specified in hydra.main().
The config_path is relative to the Python file declaring @hydra.main()
--config_name,-cn : Overrides the config_name specified in hydra.main()
--info,-i : Print Hydra information
Overrides : Any key=value arguments to override config values (use dots for.nested=overrides)
""",
id="overriding_help_template:$FLAGS_HELP",
Expand Down Expand Up @@ -600,6 +601,7 @@ def test_sweep_complex_defaults(
--config_path,-cp : Overrides the config_path specified in hydra.main().
The config_path is relative to the Python file declaring @hydra.main()
--config_name,-cn : Overrides the config_name specified in hydra.main()
--info,-i : Print Hydra information
Overrides : Any key=value arguments to override config values (use dots for.nested=overrides)
""",
id="overriding_hydra_help_template:$FLAGS_HELP",
Expand Down
Loading

0 comments on commit 953d155

Please sign in to comment.