def test_load_config_file_with_schema_validation( self, hydra_restore_singletons: Any, path: str) -> None: with ConfigStoreWithProvider("this_test") as cs: cs.store(name="config", node=TopLevelConfig) cs.store(group="db", name="mysql", node=MySQLConfig, package="db") config_loader = ConfigLoaderImpl( config_search_path=create_config_search_path(path)) cfg = config_loader.load_configuration( config_name="config", overrides=["+db=mysql"], strict=False, run_mode=RunMode.RUN, ) with open_dict(cfg): del cfg["hydra"] assert cfg == { "normal_yaml_config": True, "db": { "driver": "mysql", "host": "???", "port": "???", "user": "******", "password": "******", }, } expected = hydra_load_list.copy() expected.append(LoadTrace("config", path, "main", "this_test")) expected.append(LoadTrace("db/mysql", path, "main", "this_test")) assert config_loader.get_load_history() == expected
def _load_config_impl(self, input_file: str, record_load: bool = True) -> Optional[DictConfig]: """ :param input_file: :param record_load: :return: the loaded config or None if it was not found """ ret = self.repository.load_config(config_path=input_file) if record_load: if ret is None: self.all_config_checked.append( LoadTrace(filename=input_file, path=None, provider=None)) else: self.all_config_checked.append( LoadTrace( filename=input_file, path=ret.path, provider=ret.provider, )) if ret is not None: if not isinstance(ret.config, DictConfig): raise ValueError( f"Config {input_file} must be a Dictionary, got {type(ret).__name__}" ) return ret.config else: return None
def test_load_history(self, path: str) -> None: config_loader = ConfigLoaderImpl( config_search_path=create_config_search_path(path) ) cfg = config_loader.load_configuration( config_name="missing-optional-default.yaml", overrides=[], run_mode=RunMode.RUN, ) expected = deepcopy(hydra_load_list) expected.append( LoadTrace( config_group=None, config_name="missing-optional-default.yaml", provider="main", search_path=path, ) ) expected.append( LoadTrace( config_group="foo", config_name="missing", skip_reason="missing_optional_config", parent="missing-optional-default.yaml", ) ) assert_same_composition_trace(cfg.hydra.composition_trace, expected)
def test_mixed_composition_order() -> None: """ Tests that the order of mixed composition (defaults contains both config group and non config group items) is correct """ config_loader = ConfigLoaderImpl( config_search_path=create_config_search_path( "hydra/test_utils/configs")) config_loader.load_configuration( config_name="mixed_compose.yaml", overrides=[], strict=False, run_mode=RunMode.RUN, ) expected = hydra_load_list.copy() expected.extend([ LoadTrace("mixed_compose.yaml", "file://hydra/test_utils/configs", "main", None), LoadTrace("some_config", "file://hydra/test_utils/configs", "main", None), LoadTrace("group1/file1", "file://hydra/test_utils/configs", "main", None), LoadTrace("config", "file://hydra/test_utils/configs", "main", None), ]) assert config_loader.get_load_history() == expected
def test_non_config_group_default() -> None: config_loader = ConfigLoaderImpl( config_search_path=create_config_search_path("hydra/test_utils/configs") ) cfg = config_loader.load_configuration( config_name="non_config_group_default.yaml", overrides=[], strict=False, run_mode=RunMode.RUN, ) expected = deepcopy(hydra_load_list) expected.extend( [ LoadTrace( config_path="some_config", package="", parent="non_config_group_default.yaml", search_path="file://hydra/test_utils/configs", provider="main", ), LoadTrace( config_path="non_config_group_default.yaml", package="", parent="<root>", is_self=True, search_path="file://hydra/test_utils/configs", provider="main", ), ], ) assert_same_composition_trace(cfg.hydra.composition_trace, expected)
def test_load_config_with_schema(self, hydra_restore_singletons: Any, path: str) -> None: ConfigStore.instance().store(name="config", node=TopLevelConfig, provider="this_test") ConfigStore.instance().store(group="db", name="mysql", node=MySQLConfig, provider="this_test") config_loader = ConfigLoaderImpl( config_search_path=create_config_search_path(path)) cfg = config_loader.load_configuration(config_name="config", overrides=["+db=mysql"], run_mode=RunMode.RUN) expected = deepcopy(hydra_load_list) expected.append( LoadTrace( config_name="config", search_path=path, provider="main", schema_provider="this_test", )) expected.append( LoadTrace( config_group="db", config_name="mysql", search_path=path, provider="main", schema_provider="this_test", parent="overrides", )) assert_same_composition_trace(cfg.hydra.composition_trace, expected) with open_dict(cfg): del cfg["hydra"] assert cfg == { "normal_yaml_config": True, "db": { "driver": "mysql", "host": "???", "port": "???", "user": "******", "password": "******", }, } # verify illegal modification is rejected at runtime with pytest.raises(ValidationError): cfg.db.port = "fail" # verify illegal override is rejected during load with pytest.raises(HydraException): config_loader.load_configuration(config_name="db/mysql", overrides=["db.port=fail"], run_mode=RunMode.RUN)
def test_load_history(self, path: str) -> None: config_loader = ConfigLoaderImpl( config_search_path=create_config_search_path(path) ) config_loader.load_configuration( config_name="missing-optional-default.yaml", overrides=[], strict=False ) expected = hydra_load_list.copy() expected.append(LoadTrace("missing-optional-default.yaml", path, "main", None)) expected.append(LoadTrace("foo/missing", None, None, None)) assert config_loader.get_load_history() == expected
def test_mixed_composition_order() -> None: """ Tests that the order of mixed composition (defaults contains both config group and non config group items) is correct """ config_loader = ConfigLoaderImpl( config_search_path=create_config_search_path("hydra/test_utils/configs") ) cfg = config_loader.load_configuration( config_name="mixed_compose.yaml", overrides=[], strict=False, run_mode=RunMode.RUN, ) expected = deepcopy(hydra_load_list) expected.extend( [ LoadTrace( config_path="some_config", package="", parent="mixed_compose.yaml", search_path="file://hydra/test_utils/configs", provider="main", ), LoadTrace( config_path="group1/file1", package="", parent="mixed_compose.yaml", search_path="file://hydra/test_utils/configs", provider="main", ), LoadTrace( config_path="config", package="", parent="mixed_compose.yaml", search_path="file://hydra/test_utils/configs", provider="main", ), LoadTrace( config_path="mixed_compose.yaml", package="", parent="<root>", is_self=True, search_path="file://hydra/test_utils/configs", provider="main", ), ] ) assert_same_composition_trace(cfg.hydra.composition_trace, expected)
def test_load_config_file_with_schema_validation( self, hydra_restore_singletons: Any, path: str ) -> None: with ConfigStoreWithProvider("this_test") as cs: cs.store(name="config", node=TopLevelConfig) cs.store(group="db", name="mysql", node=MySQLConfig, package="db") config_loader = ConfigLoaderImpl( config_search_path=create_config_search_path(path) ) cfg = config_loader.load_configuration( config_name="config", overrides=["+db=mysql"], strict=False, run_mode=RunMode.RUN, ) expected = deepcopy(hydra_load_list) expected.extend( [ LoadTrace( config_path="config", package="", parent="<root>", search_path=path, provider="main", ), LoadTrace( config_path="db/mysql", package="db", parent="<root>", search_path=path, provider="main", ), ] ) assert_same_composition_trace(cfg.hydra.composition_trace, expected) with open_dict(cfg): del cfg["hydra"] assert cfg == { "normal_yaml_config": True, "db": { "driver": "mysql", "host": "???", "port": "???", "user": "******", "password": "******", }, }
def test_override_hydra_config_group_from_config_file() -> None: config_loader = ConfigLoaderImpl( config_search_path=create_config_search_path( "hydra/test_utils/configs")) config_loader.load_configuration( config_name="overriding_logging_default.yaml", overrides=[], strict=False, run_mode=RunMode.RUN, ) # This load history is too different to easily reuse the standard hydra_load_list assert config_loader.get_load_history() == [ LoadTrace("hydra_config", "structured://", "hydra", None), LoadTrace("hydra/hydra_logging/hydra_debug", "pkg://hydra.conf", "hydra", None), LoadTrace("hydra/job_logging/disabled", "pkg://hydra.conf", "hydra", None), LoadTrace("hydra/sweeper/basic", "structured://", "hydra", None), LoadTrace("hydra/output/default", "pkg://hydra.conf", "hydra", None), LoadTrace("hydra/help/default", "pkg://hydra.conf", "hydra", None), LoadTrace("hydra/hydra_help/default", "pkg://hydra.conf", "hydra", None), LoadTrace( "overriding_logging_default.yaml", "file://hydra/test_utils/configs", "main", None, ), ]
def _compose_config_from_defaults_list( self, defaults: List[DefaultElement], repo: IConfigRepository, ) -> Tuple[DictConfig, List[LoadTrace]]: composition_trace = [] cfg = OmegaConf.create() for default in defaults: if default.skip_load: trace = LoadTrace( config_group=default.config_group, config_name=default.config_name, package=default.get_subject_package(), parent=default.parent, skip_reason=default.skip_load_reason, ) else: loaded, trace = self._load_single_config(default=default, repo=repo) # should not happen, generation of defaults list already verified that this exists merged = OmegaConf.merge(cfg, loaded.config) assert isinstance(merged, DictConfig) cfg = merged assert cfg is not None composition_trace.append(trace) # This is primarily cosmetic cfg._metadata.ref_type = cfg._metadata.object_type return cfg, composition_trace
def test_load_schema_as_config(hydra_restore_singletons: Any) -> None: """ Load structured config as a configuration """ ConfigStore.instance().store(name="config", node=TopLevelConfig, provider="this_test") ConfigStore.instance().store( name="db/mysql", node=MySQLConfig, provider="this_test", ) config_loader = ConfigLoaderImpl( config_search_path=create_config_search_path(None)) cfg = config_loader.load_configuration(config_name="config", overrides=[], run_mode=RunMode.RUN) with open_dict(cfg): del cfg["hydra"] assert cfg == { "normal_yaml_config": "???", "db": { "driver": MISSING, "host": MISSING, "port": MISSING, "user": MISSING, "password": MISSING, }, } expected = hydra_load_list.copy() expected.extend([LoadTrace("config", "structured://", "this_test", None)]) assert config_loader.get_load_history() == expected
def test_load_schema_as_config(restore_singletons: Any) -> None: """ Load structured config as a configuration """ ConfigStore.instance().store( group="db", name="mysql", node=MySQLConfig, provider="test_provider", ) config_loader = ConfigLoaderImpl( config_search_path=create_config_search_path(None)) cfg = config_loader.load_configuration(config_name="db/mysql", overrides=[]) with open_dict(cfg): del cfg["hydra"] assert cfg == { "db": { "driver": MISSING, "host": MISSING, "port": MISSING, "user": MISSING, "password": MISSING, } } expected = hydra_load_list.copy() expected.extend( [LoadTrace("db/mysql", "structured://", "test_provider", None)]) assert config_loader.get_load_history() == expected
def test_load_config_file_with_schema_validation(self, restore_singletons: Any, path: str) -> None: with ConfigStoreWithProvider("test_provider") as config_store: config_store.store(group="db", name="mysql", node=MySQLConfig, package="db") config_loader = ConfigLoaderImpl( config_search_path=create_config_search_path(path)) cfg = config_loader.load_configuration(config_name="db/mysql", overrides=[], strict=False) with open_dict(cfg): del cfg["hydra"] assert cfg == { "db": { "driver": "mysql", "host": "???", "port": "???", "user": "******", "password": "******", } } expected = hydra_load_list.copy() expected.append(LoadTrace("db/mysql", path, "main", "test_provider")) assert config_loader.get_load_history() == expected
def test_non_config_group_default() -> None: config_loader = ConfigLoaderImpl( config_search_path=create_config_search_path( "hydra/test_utils/configs")) config_loader.load_configuration( config_name="non_config_group_default.yaml", overrides=[], strict=False) expected = hydra_load_list.copy() expected.extend([ LoadTrace( "non_config_group_default.yaml", "file://hydra/test_utils/configs", "main", None, ), LoadTrace("some_config", "file://hydra/test_utils/configs", "main", None), ]) assert config_loader.get_load_history() == expected
def test_load_history_with_basic_launcher(self, path: str) -> None: config_loader = ConfigLoaderImpl( config_search_path=create_config_search_path(path)) config_loader.load_configuration( config_name="custom_default_launcher.yaml", overrides=["hydra/launcher=basic"], strict=False, ) expected = hydra_load_list.copy() expected.append( LoadTrace("custom_default_launcher.yaml", path, "main", None)) assert config_loader.get_load_history() == expected
def test_default_removal(config_file: str, overrides: List[str]) -> None: config_loader = ConfigLoaderImpl( config_search_path=create_config_search_path( "hydra/test_utils/configs")) config_loader.load_configuration(config_name=config_file, overrides=overrides, strict=False) expected = list( filter(lambda x: x.filename != "hydra/launcher/basic", hydra_load_list.copy())) expected.append( LoadTrace(config_file, "file://hydra/test_utils/configs", "main", None)) assert config_loader.get_load_history() == expected
def _print_composition_trace(self, cfg: DictConfig) -> None: # Print configurations used to compose the config object assert log is not None log.debug("") self._log_header("Composition trace", filler="*") box: List[List[str]] = [[ "Config group", "Config name", "Search path", "Provider", "Schema provider", ]] composition_trace = [ LoadTrace(**x) for x in cfg.hydra.composition_trace ] for trace in composition_trace: box.append([ trace.config_group if trace.config_group is not None else "", trace.config_name if trace.config_name is not None else "", trace.search_path if trace.search_path is not None else "", trace.provider if trace.provider is not None else "", trace.schema_provider if trace.schema_provider is not None else "", ]) padding = get_column_widths(box) del box[0] header = "| {} | {} | {} | {} | {} |".format( "Config group".ljust(padding[0]), "Config name".ljust(padding[1]), "Search path".ljust(padding[2]), "Provider".ljust(padding[3]), "Schema provider".ljust(padding[4]), ) self._log_header(header=header, filler="-") for row in box: log.debug("| {} | {} | {} | {} | {} |".format( row[0].ljust(padding[0]), row[1].ljust(padding[1]), row[2].ljust(padding[2]), row[3].ljust(padding[3]), row[4].ljust(padding[4]), )) self._log_footer(header=header, filler="-")
def record_loading( name: str, path: Optional[str], provider: Optional[str], schema_provider: Optional[str], ) -> Optional[LoadTrace]: trace = LoadTrace( filename=name, path=path, provider=provider, schema_provider=schema_provider, ) if record_load: self.all_config_checked.append(trace) return trace
def test_load_history_with_basic_launcher(self, path: str) -> None: config_loader = ConfigLoaderImpl( config_search_path=create_config_search_path(path)) cfg = config_loader.load_configuration( config_name="custom_default_launcher.yaml", overrides=["hydra/launcher=basic"], strict=False, run_mode=RunMode.RUN, ) expected = deepcopy(hydra_load_list) expected.append( LoadTrace( config_name="custom_default_launcher.yaml", search_path=path, provider="main", )) assert_same_composition_trace(cfg.hydra.composition_trace, expected)
def test_load_config_with_schema( self, restore_singletons: Any, path: str # noqa: F811 ) -> None: ConfigStore.instance().store( group="db", name="mysql", node=MySQLConfig, path="db", provider="test_provider", ) config_loader = ConfigLoaderImpl( config_search_path=create_config_search_path(path)) cfg = config_loader.load_configuration(config_name="db/mysql", overrides=[]) del cfg["hydra"] assert cfg == { "db": { "driver": "mysql", "host": "???", "port": "???", "user": "******", "password": "******", } } expected = hydra_load_list.copy() expected.append(LoadTrace("db/mysql", path, "main", "test_provider")) assert config_loader.get_load_history() == expected # verify illegal modification is rejected at runtime with pytest.raises(ValidationError): cfg.db.port = "fail" # verify illegal override is rejected during load with pytest.raises(ValidationError): config_loader.load_configuration(config_name="db/mysql", overrides=["db.port=fail"])
def test_load_history(self, path: str) -> None: config_loader = ConfigLoaderImpl( config_search_path=create_config_search_path(path)) cfg = config_loader.load_configuration( config_name="missing-optional-default.yaml", overrides=[], run_mode=RunMode.RUN, ) expected = deepcopy(hydra_load_list) expected.append( LoadTrace( config_path="missing-optional-default.yaml", package="", parent="<root>", is_self=True, search_path=path, provider="main", )) assert_same_composition_trace(cfg.hydra.composition_trace, expected)
def test_load_schema_as_config(hydra_restore_singletons: Any) -> None: """ Load structured config as a configuration """ ConfigStore.instance().store( name="config", node=TopLevelConfig, provider="this_test" ) ConfigStore.instance().store( name="db/mysql", node=MySQLConfig, provider="this_test" ) config_loader = ConfigLoaderImpl(config_search_path=create_config_search_path(None)) cfg = config_loader.load_configuration( config_name="config", overrides=[], run_mode=RunMode.RUN ) expected = deepcopy(hydra_load_list) expected.append( LoadTrace( config_path="config", package="", parent="<root>", search_path="structured://", provider="this_test", ) ) assert_same_composition_trace(cfg.hydra.composition_trace, expected) with open_dict(cfg): del cfg["hydra"] assert cfg == { "normal_yaml_config": "???", "db": { "driver": MISSING, "host": MISSING, "port": MISSING, "user": MISSING, "password": MISSING, }, }
def test_default_removal(config_file: str, overrides: List[str]) -> None: config_loader = ConfigLoaderImpl( config_search_path=create_config_search_path("hydra/test_utils/configs") ) cfg = config_loader.load_configuration( config_name=config_file, overrides=overrides, strict=False, run_mode=RunMode.RUN ) expected = deepcopy(hydra_load_list) for x in expected: if x.config_group == "hydra/launcher": x.skip_reason = "deleted_from_list" x.search_path = None x.provider = None expected.append( LoadTrace( config_name=config_file, search_path="file://hydra/test_utils/configs", provider="main", ) ) assert_same_composition_trace(cfg.hydra.composition_trace, expected)
def test_default_removal(config_file: str, overrides: List[str]) -> None: config_loader = ConfigLoaderImpl( config_search_path=create_config_search_path("hydra/test_utils/configs") ) cfg = config_loader.load_configuration( config_name=config_file, overrides=overrides, strict=False, run_mode=RunMode.RUN ) expected = deepcopy(hydra_load_list) expected = list( filterfalse(lambda x: x.config_path == "hydra/launcher/basic", expected) ) expected.append( LoadTrace( config_path=config_file, package="", parent="<root>", search_path="file://hydra/test_utils/configs", provider="main", ) ) assert_same_composition_trace(cfg.hydra.composition_trace, expected)
def assert_same_composition_trace(composition_trace: Any, expected: Any) -> None: actual = [LoadTrace(**x) for x in composition_trace] assert actual == expected
def test_override_hydra_config_group_from_config_file() -> None: config_loader = ConfigLoaderImpl( config_search_path=create_config_search_path("hydra/test_utils/configs") ) cfg = config_loader.load_configuration( config_name="overriding_logging_default.yaml", overrides=[], strict=False, run_mode=RunMode.RUN, ) expected = [ LoadTrace( config_path="hydra/output/default", package="hydra", parent="hydra/config", search_path="pkg://hydra.conf", provider="hydra", ), LoadTrace( config_path="hydra/launcher/basic", package="hydra.launcher", parent="hydra/config", search_path="structured://", provider="hydra", ), LoadTrace( config_path="hydra/sweeper/basic", package="hydra.sweeper", parent="hydra/config", search_path="structured://", provider="hydra", ), LoadTrace( config_path="hydra/help/default", package="hydra.help", parent="hydra/config", search_path="pkg://hydra.conf", provider="hydra", ), LoadTrace( config_path="hydra/hydra_help/default", package="hydra.hydra_help", parent="hydra/config", search_path="pkg://hydra.conf", provider="hydra", ), LoadTrace( config_path="hydra/hydra_logging/hydra_debug", package="hydra.hydra_logging", parent="hydra/config", search_path="pkg://hydra.conf", provider="hydra", ), LoadTrace( config_path="hydra/job_logging/disabled", package="hydra.job_logging", parent="hydra/config", search_path="pkg://hydra.conf", provider="hydra", ), LoadTrace( config_path="hydra/config", package="hydra", parent="<root>", is_self=True, search_path="structured://", provider="hydra", ), LoadTrace( config_path="overriding_logging_default.yaml", package="", parent="<root>", search_path="file://hydra/test_utils/configs", provider="main", ), ] assert_same_composition_trace(cfg.hydra.composition_trace, expected)
port: int = MISSING user: str = MISSING password: str = MISSING @dataclass class TopLevelConfig: normal_yaml_config: bool = MISSING db: MySQLConfig = MySQLConfig() hydra_load_list: List[LoadTrace] = [ LoadTrace( config_path="hydra/output/default", package="hydra", parent="hydra/config", search_path="pkg://hydra.conf", provider="hydra", ), LoadTrace( config_path="hydra/launcher/basic", package="hydra.launcher", parent="hydra/config", search_path="structured://", provider="hydra", ), LoadTrace( config_path="hydra/sweeper/basic", package="hydra.sweeper", parent="hydra/config", search_path="structured://",
class MySQLConfig: driver: str = MISSING host: str = MISSING port: int = MISSING user: str = MISSING password: str = MISSING @dataclass class TopLevelConfig: normal_yaml_config: bool = MISSING db: MySQLConfig = MySQLConfig() hydra_load_list: List[LoadTrace] = [ LoadTrace("hydra_config", "structured://", "hydra", None), LoadTrace("hydra/hydra_logging/default", "pkg://hydra.conf", "hydra", None), LoadTrace("hydra/job_logging/default", "pkg://hydra.conf", "hydra", None), LoadTrace("hydra/launcher/basic", "structured://", "hydra", None), LoadTrace("hydra/sweeper/basic", "structured://", "hydra", None), LoadTrace("hydra/output/default", "pkg://hydra.conf", "hydra", None), LoadTrace("hydra/help/default", "pkg://hydra.conf", "hydra", None), LoadTrace("hydra/hydra_help/default", "pkg://hydra.conf", "hydra", None), ] @pytest.mark.parametrize( "path", [ pytest.param("file://hydra/test_utils/configs", id="file"),
def _load_single_config( self, default: DefaultElement, repo: IConfigRepository, ) -> Tuple[ConfigResult, LoadTrace]: config_path = default.config_path() package_override = default.package ret = repo.load_config( config_path=config_path, is_primary_config=default.primary, package_override=package_override, ) assert ret is not None if not isinstance(ret.config, DictConfig): raise ValueError( f"Config {config_path} must be a Dictionary, got {type(ret).__name__}" ) default.search_path = ret.path schema_provider = None if not ret.is_schema_source: schema = None try: schema_source = repo.get_schema_source() schema = schema_source.load_config( ConfigSource._normalize_file_name(filename=config_path), is_primary_config=default.primary, package_override=package_override, ) except ConfigLoadError: # schema not found, ignore pass if schema is not None: try: # if config has a hydra node, remove it during validation and add it back. # This allows overriding Hydra's configuration without declaring this node # in every program hydra = None hydra_config_group = ( default.config_group is not None and default.config_group.startswith("hydra/")) if "hydra" in ret.config and not hydra_config_group: hydra = ret.config.pop("hydra") schema_provider = schema.provider merged = OmegaConf.merge(schema.config, ret.config) assert isinstance(merged, DictConfig) if hydra is not None: with open_dict(merged): merged.hydra = hydra ret.config = merged except OmegaConfBaseException as e: raise ConfigCompositionException( f"Error merging '{config_path}' with schema") from e assert isinstance(merged, DictConfig) trace = LoadTrace( config_group=default.config_group, config_name=default.config_name, package=default.get_subject_package(), search_path=ret.path, parent=default.parent, provider=ret.provider, schema_provider=schema_provider, ) return ret, trace