Skip to content

OmegaConfigLoader

kedro.config.OmegaConfigLoader

OmegaConfigLoader(conf_source, env=None, runtime_params=None, *, config_patterns=None, base_env=None, default_run_env=None, custom_resolvers=None, merge_strategy=None, ignore_hidden=True, restrict_runtime_params_type_selection=False)

Bases: AbstractConfigLoader

Recursively scan directories (config paths) contained in conf_source for configuration files with a yaml, yml or json extension, load and merge them through OmegaConf (https://omegaconf.readthedocs.io/) and return them in the form of a config dictionary.

The first processed config path is the base directory inside conf_source. The optional env argument can be used to specify a subdirectory of conf_source to process as a config path after base.

When the same top-level key appears in any two config files located in the same (sub)directory, a ValueError is raised.

When the same key appears in any two config files located in different (sub)directories, the last processed config path takes precedence and overrides this key and any sub-keys.

You can access the different configurations as follows:

import logging.config
from kedro.config import OmegaConfigLoader
from kedro.framework.project import settings

conf_path = str(project_path / settings.CONF_SOURCE)
conf_loader = OmegaConfigLoader(conf_source=conf_path, env="local")

conf_catalog = conf_loader["catalog"]
conf_params = conf_loader["parameters"]

OmegaConf supports variable interpolation in configuration https://omegaconf.readthedocs.io/en/2.2_branch/usage.html#merging-configurations. It is recommended to use this instead of yaml anchors with the OmegaConfigLoader.

This version of the OmegaConfigLoader does not support any of the built-in OmegaConf resolvers. Support for resolvers might be added in future versions.

To use this class, change the setting for the CONFIG_LOADER_CLASS constant in settings.py.

Example:

# in settings.py
from kedro.config import OmegaConfigLoader

CONFIG_LOADER_CLASS = OmegaConfigLoader

Source code in kedro/config/omegaconf_config.py
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
def __init__(  # noqa: PLR0913
    self,
    conf_source: str | Path,
    env: str | None = None,
    runtime_params: dict[str, Any] | None = None,
    *,
    config_patterns: dict[str, list[str]] | None = None,
    base_env: str | None = None,
    default_run_env: str | None = None,
    custom_resolvers: dict[str, Callable] | None = None,
    merge_strategy: dict[str, str] | None = None,
    ignore_hidden: bool = True,
    restrict_runtime_params_type_selection: bool = False,
):
    if isinstance(conf_source, Path):
        conf_source = str(conf_source)
    """Instantiates a ``OmegaConfigLoader``.

    Args:
        conf_source: Path to use as root directory for loading configuration.
            This can be a local filesystem path or a remote URL with protocol
            (e.g., s3://, gs://, etc.)
        env: Environment that will take precedence over base.
        runtime_params: Extra parameters passed to a Kedro run.
        config_patterns: Regex patterns that specify the naming convention for configuration
            files so they can be loaded. Can be customised by supplying config_patterns as
            in `CONFIG_LOADER_ARGS` in `settings.py`.
        base_env: Name of the base environment. When the ``OmegaConfigLoader`` is used directly
            this defaults to `None`. Otherwise, the value will come from the `CONFIG_LOADER_ARGS` in the project
            settings, where base_env defaults to `"base"`.
            This is used in the `conf_paths` property method to construct
            the configuration paths.
        default_run_env: Name of the default run environment. When the ``OmegaConfigLoader`` is used directly
            this defaults to `None`. Otherwise, the value will come from the `CONFIG_LOADER_ARGS` in the project
            settings, where default_run_env defaults to `"local"`.
            Can be overridden by supplying the `env` argument.
        custom_resolvers: A dictionary of custom resolvers to be registered. For more information,
            see here: https://omegaconf.readthedocs.io/en/2.3_branch/custom_resolvers.html#custom-resolvers
        merge_strategy: A dictionary that specifies the merging strategy for each configuration type.
            The accepted merging strategies are `soft` and `destructive`. Defaults to `destructive`.
        ignore_hidden: A boolean flag that determines whether hidden files and directories should be
            ignored when loading configuration files. If True, ignore hidden files and files in hidden directories.
        restrict_runtime_params_type_selection: When True, a catalog dataset's `type` is not
            allowed to resolve through the `runtime_params:` resolver (e.g. `type:
            "${runtime_params:dataset.type}"`) at all -- `runtime_params` must not be able to
            choose which class gets dynamically imported and instantiated. Defaults to False,
            since `runtime_params` is normally supplied by a trusted caller (e.g. `kedro run
            --params`). Callers that construct this loader with `runtime_params` sourced from
            an untrusted caller (e.g. an HTTP request body) must pass True.
    """
    self.ignore_hidden = ignore_hidden
    self.restrict_runtime_params_type_selection = (
        restrict_runtime_params_type_selection
    )
    self.base_env = base_env or ""
    self.default_run_env = default_run_env or ""
    self.merge_strategy = merge_strategy or {}
    self._globals_oc: DictConfig | None = None
    self._runtime_params_oc: DictConfig | None = None
    # Paths where runtime_params actually resolved, not a resolver default.
    self._runtime_params_hits: set[str] = set()
    self.config_patterns = {
        "catalog": ["catalog*", "catalog*/**", "**/catalog*"],
        "parameters": ["parameters*", "parameters*/**", "**/parameters*"],
        "credentials": ["credentials*", "credentials*/**", "**/credentials*"],
        "globals": ["globals.yml"],
    }
    self.config_patterns.update(config_patterns or {})

    # Deactivate oc.env built-in resolver for OmegaConf
    OmegaConf.clear_resolver("oc.env")
    # Register user provided custom resolvers
    self._custom_resolvers = custom_resolvers
    if custom_resolvers:
        self._register_new_resolvers(custom_resolvers)
    # Register globals resolver
    self._register_globals_resolver()

    # Setup file system and protocol
    self._fs, self._protocol = self._initialise_filesystem_and_protocol(conf_source)

    # Store remote root path if using cloud protocol
    if self._protocol in CLOUD_PROTOCOLS or self._protocol in HTTP_PROTOCOLS:
        options = _parse_filepath(conf_source)
        self._remote_root_path = options["path"].rstrip("/")

    super().__init__(
        conf_source=conf_source,
        env=env,
        runtime_params=runtime_params,
    )
    try:
        self._globals = self["globals"]
    except MissingConfigException:
        self._globals = {}

_custom_resolvers instance-attribute

_custom_resolvers = custom_resolvers

_globals instance-attribute

_globals = self['globals']

_globals_oc instance-attribute

_globals_oc = None

_remote_root_path instance-attribute

_remote_root_path = options['path'].rstrip('/')

_runtime_params_hits instance-attribute

_runtime_params_hits = set()

_runtime_params_oc instance-attribute

_runtime_params_oc = None

base_env instance-attribute

base_env = base_env or ''

config_patterns instance-attribute

config_patterns = {'catalog': ['catalog*', 'catalog*/**', '**/catalog*'], 'parameters': ['parameters*', 'parameters*/**', '**/parameters*'], 'credentials': ['credentials*', 'credentials*/**', '**/credentials*'], 'globals': ['globals.yml']}

default_run_env instance-attribute

default_run_env = default_run_env or ''

ignore_hidden instance-attribute

ignore_hidden = ignore_hidden

merge_strategy instance-attribute

merge_strategy = merge_strategy or {}

restrict_runtime_params_type_selection instance-attribute

restrict_runtime_params_type_selection = restrict_runtime_params_type_selection

__getitem__

__getitem__(key)

Get configuration files by key, load and merge them, and return them in the form of a config dictionary.

Parameters:

  • key (str) –

    Key of the configuration type to fetch.

Raises:

  • KeyError –

    If key provided isn't present in the config_patterns of this OmegaConfigLoader instance.

  • MissingConfigException –

    If no configuration files exist matching the patterns mapped to the provided key.

Returns:

  • Dict[str, Any] –

    A Python dictionary with the combined configuration from all configuration files.

Source code in kedro/config/omegaconf_config.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
def __getitem__(self, key: str) -> dict[str, Any]:
    """Get configuration files by key, load and merge them, and
    return them in the form of a config dictionary.

    Args:
        key: Key of the configuration type to fetch.

    Raises:
        KeyError: If key provided isn't present in the config_patterns of this
            ``OmegaConfigLoader`` instance.
        MissingConfigException: If no configuration files exist matching the patterns
            mapped to the provided key.

    Returns:
        Dict[str, Any]:  A Python dictionary with the combined
            configuration from all configuration files.
    """
    # Allow bypassing of loading config from patterns if a key and value have been set
    # explicitly on the ``OmegaConfigLoader`` instance.

    # Re-register runtime params resolver in case it was previously deactivated
    self._register_runtime_params_resolver()

    if key in self:
        return super().__getitem__(key)  # type: ignore[no-any-return]

    if key not in self.config_patterns:
        raise KeyError(
            f"No config patterns were found for '{key}' in your config loader"
        )

    base_configs, env_configs, base_path, env_path, processed_files = (
        self._read_raw_config(key)
    )
    return self._resolve_from_raw_config(
        key, base_configs, env_configs, base_path, env_path, processed_files
    )

__repr__

__repr__()
Source code in kedro/config/omegaconf_config.py
326
327
328
329
330
331
332
333
334
335
def __repr__(self) -> str:  # pragma: no cover
    return (
        f"OmegaConfigLoader(conf_source={self.conf_source}, env={self.env}, "
        f"runtime_params={self.runtime_params}, "
        f"config_patterns={self.config_patterns}, "
        f"base_env={self.base_env}), "
        f"default_run_env={self.default_run_env}), "
        f"custom_resolvers={self._custom_resolvers}), "
        f"merge_strategy={self.merge_strategy})"
    )

__setitem__

__setitem__(key, value)
Source code in kedro/config/omegaconf_config.py
196
197
198
199
200
def __setitem__(self, key: str, value: Any) -> None:
    if key == "globals":
        # Update the cached value at self._globals since it is used by the globals resolver
        self._globals = value
    super().__setitem__(key, value)

_check_duplicates

_check_duplicates(key, config_per_file)
Source code in kedro/config/omegaconf_config.py
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
def _check_duplicates(self, key: str, config_per_file: dict[Path, Any]) -> None:
    if key == "parameters":
        seen_files_to_keys = {
            file: self._get_all_keys(OmegaConf.to_container(config, resolve=False))
            for file, config in config_per_file.items()
        }
    else:
        seen_files_to_keys = {
            file: set(config.keys()) for file, config in config_per_file.items()
        }

    duplicates = []

    filepaths = list(seen_files_to_keys.keys())
    for i, filepath1 in enumerate(filepaths, 1):
        config1 = seen_files_to_keys[filepath1]
        for filepath2 in filepaths[i:]:
            config2 = seen_files_to_keys[filepath2]

            combined_keys = config1 & config2
            overlapping_keys = {
                key for key in combined_keys if not key.startswith("_")
            }

            if overlapping_keys:
                sorted_keys = ", ".join(sorted(overlapping_keys))
                if len(sorted_keys) > 100:  # noqa: PLR2004
                    sorted_keys = sorted_keys[:100] + "..."
                duplicates.append(
                    f"Duplicate keys found in {filepath1} and {filepath2}: {sorted_keys}"
                )

    if duplicates:
        dup_str = "\n".join(duplicates)
        raise ValueError(f"{dup_str}")

_destructive_merge staticmethod

_destructive_merge(config, env_config, env_path)
Source code in kedro/config/omegaconf_config.py
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
@staticmethod
def _destructive_merge(
    config: dict[str, Any], env_config: dict[str, Any], env_path: str
) -> dict[str, Any]:
    # Destructively merge the two env dirs. The chosen env will override base.
    common_keys = config.keys() & env_config.keys()
    if common_keys:
        sorted_keys = ", ".join(sorted(common_keys))
        msg = (
            "Config from path '%s' will override the following "
            "existing top-level config keys: %s"
        )
        _config_logger.debug(msg, env_path, sorted_keys)

    config.update(env_config)
    return config

_get_all_keys

_get_all_keys(cfg, parent_key='')
Source code in kedro/config/omegaconf_config.py
615
616
617
618
619
620
621
622
623
624
def _get_all_keys(self, cfg: Any, parent_key: str = "") -> set[str]:
    keys: set[str] = set()

    for key, value in cfg.items():
        full_key = f"{parent_key}.{key}" if parent_key else key
        if isinstance(value, dict):
            keys.update(self._get_all_keys(value, full_key))
        else:
            keys.add(full_key)
    return keys

_get_conf_env_path

_get_conf_env_path(env)

Get the full path to the configuration environment directory.

Source code in kedro/config/omegaconf_config.py
267
268
269
270
271
272
273
def _get_conf_env_path(self, env: str) -> str:
    """Get the full path to the configuration environment directory."""
    if self._protocol in CLOUD_PROTOCOLS or self._protocol in HTTP_PROTOCOLS:
        return f"{self._remote_root_path}/{env}"
    if self._protocol == "file":
        return str(Path(self.conf_source) / env)
    return str(Path(self._fs.ls("", detail=False)[-1]) / env)

_get_globals_value

_get_globals_value(variable, default_value=_NO_VALUE)

Return the globals values to the resolver

Source code in kedro/config/omegaconf_config.py
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
def _get_globals_value(self, variable: str, default_value: Any = _NO_VALUE) -> Any:
    """Return the globals values to the resolver"""
    if variable.startswith("_"):
        raise InterpolationResolutionError(
            "Keys starting with '_' are not supported for globals."
        )

    if not self._globals_oc:
        self._globals_oc = OmegaConf.create(self._globals)

    interpolated_value = OmegaConf.select(
        self._globals_oc, variable, default=default_value
    )
    if interpolated_value != _NO_VALUE:
        return interpolated_value
    else:
        raise InterpolationResolutionError(
            f"Globals key '{variable}' not found and no default value provided."
        )

_get_runtime_value

_get_runtime_value(variable, default_value=_NO_VALUE)

Return the runtime params values to the resolver

Source code in kedro/config/omegaconf_config.py
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
def _get_runtime_value(self, variable: str, default_value: Any = _NO_VALUE) -> Any:
    """Return the runtime params values to the resolver"""
    if not self._runtime_params_oc:
        self._runtime_params_oc = OmegaConf.create(self.runtime_params)

    # Resolved separately from the `default=` lookup below so we can tell
    # a real runtime_params value apart from a resolver default/fallback
    # (e.g. `${runtime_params:type, ${globals:...}}`) -- only the former
    # is attacker-influenced when runtime_params comes from an untrusted
    # caller, and only the former is tracked in `_runtime_params_hits`.
    found_value = OmegaConf.select(
        self._runtime_params_oc, variable, default=_NO_VALUE
    )
    if found_value is not _NO_VALUE:
        self._runtime_params_hits.add(variable)
        return found_value

    if default_value is not _NO_VALUE:
        return default_value

    raise InterpolationResolutionError(
        f"Runtime parameter '{variable}' not found and no default value provided."
    )

_guard_runtime_params_in_catalog_type

_guard_runtime_params_in_catalog_type(aggregate_config, merged_config_container)

Reject a dataset type that resolves through runtime_params:, when self.restrict_runtime_params_type_selection is set.

type selects the class Kedro dynamically imports and instantiates, so letting untrusted runtime params choose it allows arbitrary importable AbstractDataset subclasses, including ones with side effects on load (e.g. kedro_datasets.pickle.PickleDataset). Checked at every nesting depth to also cover wrapper datasets such as CachedDataset and PartitionedDataset.

Source code in kedro/config/omegaconf_config.py
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
@typing.no_type_check
def _guard_runtime_params_in_catalog_type(
    self,
    aggregate_config: Iterable[DictConfig],
    merged_config_container: dict[str, Any],
) -> None:
    """Reject a dataset ``type`` that resolves through ``runtime_params:``,
    when ``self.restrict_runtime_params_type_selection`` is set.

    ``type`` selects the class Kedro dynamically imports and instantiates,
    so letting untrusted runtime params choose it allows arbitrary
    importable ``AbstractDataset`` subclasses, including ones with side
    effects on load (e.g. ``kedro_datasets.pickle.PickleDataset``).
    Checked at every nesting depth to also cover wrapper datasets such as
    ``CachedDataset`` and ``PartitionedDataset``.
    """
    if not self.restrict_runtime_params_type_selection:
        return

    raw_container = OmegaConf.to_container(
        OmegaConf.unsafe_merge(*aggregate_config), resolve=False
    )

    for dataset_name, raw_entry in raw_container.items():
        resolved_entry = merged_config_container.get(dataset_name)
        for raw_node, resolved_node in self._iter_nested_dicts(
            raw_entry, resolved_entry
        ):
            raw_type = raw_node.get("type")
            if (
                not isinstance(raw_type, str)
                or _RUNTIME_PARAMS_MARKER not in raw_type
            ):
                continue

            # A `type` string can reference `runtime_params:` more than
            # once (e.g. concatenated resolver calls); treat it as
            # attacker-influenced if *any* of them actually hit a
            # runtime_params value.
            variables = [
                v.strip() for v in _RUNTIME_PARAMS_VARIABLE_RE.findall(raw_type)
            ]
            if not variables or not any(
                v in self._runtime_params_hits for v in variables
            ):
                # `runtime_params:` appears in the raw string, but no
                # actual runtime_params value was used for it -- it fell
                # back to a resolver default (e.g. a `globals:` value), so
                # it isn't attacker-influenced.
                continue

            resolved_type = resolved_node.get("type")
            raise InterpolationResolutionError(
                f"Dataset '{dataset_name}' resolves a 'type' field via "
                f"the 'runtime_params:' resolver to '{resolved_type}', "
                "which is not permitted. Runtime parameters must not "
                "select which dataset class is instantiated, at any "
                "nesting depth."
            )

_initialise_filesystem_and_protocol staticmethod

_initialise_filesystem_and_protocol(conf_source)

Set up the file system based on the file type or protocol detected in conf_source.

Source code in kedro/config/omegaconf_config.py
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
@staticmethod
def _initialise_filesystem_and_protocol(
    conf_source: str,
) -> tuple[fsspec.AbstractFileSystem, str]:
    """Set up the file system based on the file type or protocol detected in conf_source."""
    # Force string for regex
    conf_source = str(conf_source)
    # Check if it's an archive file
    file_mimetype, _ = mimetypes.guess_type(conf_source)
    if file_mimetype == "application/x-tar":
        return fsspec.filesystem(protocol="tar", fo=conf_source), "tar"
    elif file_mimetype in (
        "application/zip",
        "application/x-zip-compressed",
        "application/zip-compressed",
    ):
        return fsspec.filesystem(protocol="zip", fo=conf_source), "zip"

    # Parse to check for protocol
    options = _parse_filepath(conf_source)
    protocol = options["protocol"]

    # Create and return the appropriate filesystem
    if protocol in HTTP_PROTOCOLS or protocol in CLOUD_PROTOCOLS:
        # For HTTP and cloud storage protocols, create the appropriate filesystem
        return fsspec.filesystem(protocol=protocol), protocol
    else:
        # Default to local filesystem
        return fsspec.filesystem(protocol="file", fo=conf_source), "file"

_is_hidden

_is_hidden(path_str)

Check if path contains any hidden directory or is a hidden file

Source code in kedro/config/omegaconf_config.py
779
780
781
782
783
784
785
786
787
788
789
790
791
def _is_hidden(self, path_str: str) -> bool:
    """Check if path contains any hidden directory or is a hidden file"""
    path = Path(path_str)
    conf_path = Path(self.conf_source).resolve().as_posix()
    if self._protocol == "file":
        path = path.resolve()
    posix_path = path.as_posix()
    if posix_path.startswith(conf_path):
        posix_path = posix_path.replace(conf_path, "")
    parts = posix_path.split(self._fs.sep)  # filesystem specific separator
    HIDDEN = "."
    # Check if any component (folder or file) starts with a dot (.)
    return any(part.startswith(HIDDEN) for part in parts)

_is_valid_config_path

_is_valid_config_path(path)

Check if given path is a file path and file type is yaml or json.

Source code in kedro/config/omegaconf_config.py
626
627
628
629
630
631
632
633
def _is_valid_config_path(self, path: Path) -> bool:
    """Check if given path is a file path and file type is yaml or json."""
    posix_path = path.as_posix()
    return self._fs.isfile(str(posix_path)) and path.suffix in [
        ".yml",
        ".yaml",
        ".json",
    ]

_iter_nested_dicts staticmethod

_iter_nested_dicts(raw_node, resolved_node)

Yield (raw_node, resolved_node) for raw_node and every dict nested within it, pairing each with the corresponding resolved sub-tree by key so callers can compare a field's raw (unresolved) and resolved values at any depth.

Source code in kedro/config/omegaconf_config.py
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
@staticmethod
def _iter_nested_dicts(
    raw_node: Any, resolved_node: Any
) -> typing.Generator[tuple[dict, dict], None, None]:
    """Yield ``(raw_node, resolved_node)`` for ``raw_node`` and every dict
    nested within it, pairing each with the corresponding resolved
    sub-tree by key so callers can compare a field's raw (unresolved) and
    resolved values at any depth.
    """
    if not isinstance(raw_node, dict):
        return
    resolved_node = resolved_node if isinstance(resolved_node, dict) else {}
    yield raw_node, resolved_node
    for key, raw_value in raw_node.items():
        if isinstance(raw_value, dict):
            yield from OmegaConfigLoader._iter_nested_dicts(
                raw_value, resolved_node.get(key)
            )

_merge_and_resolve

_merge_and_resolve(key, config_per_file)

Merge pre-loaded per-file configs and resolve interpolations.

See _resolve_from_raw_config for the runtime_params locking note.

Source code in kedro/config/omegaconf_config.py
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
@typing.no_type_check
def _merge_and_resolve(
    self, key: str, config_per_file: dict[Path, DictConfig]
) -> dict[str, Any]:
    """Merge pre-loaded per-file configs and resolve interpolations.

    See ``_resolve_from_raw_config`` for the ``runtime_params`` locking note.
    """
    aggregate_config = config_per_file.values()

    if not aggregate_config:
        return {}

    if key == "parameters":
        # Merge with runtime parameters only for "parameters"
        return OmegaConf.to_container(
            OmegaConf.unsafe_merge(*aggregate_config, self.runtime_params),
            resolve=True,
        )

    merged_config_container = (
        self._resolve_catalog_config(aggregate_config)
        if key == "catalog"
        else OmegaConf.to_container(
            OmegaConf.unsafe_merge(*aggregate_config), resolve=True
        )
    )

    return {
        k: v for k, v in merged_config_container.items() if not k.startswith("_")
    }

_merge_configs

_merge_configs(config, env_config, key, env_path)
Source code in kedro/config/omegaconf_config.py
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
def _merge_configs(
    self,
    config: dict[str, Any],
    env_config: dict[str, Any],
    key: str,
    env_path: str,
) -> Any:
    merging_strategy = self.merge_strategy.get(key, "destructive")
    try:
        strategy = MergeStrategies[merging_strategy.upper()]

        # Get the corresponding merge function and call it
        merge_function_name = MERGING_IMPLEMENTATIONS[strategy]
        merge_function = getattr(self, merge_function_name)
        return merge_function(config, env_config, env_path)
    except KeyError:
        allowed_strategies = [strategy.name.lower() for strategy in MergeStrategies]
        raise ValueError(
            f"Merging strategy {merging_strategy} not supported. The accepted merging "
            f"strategies are {allowed_strategies}."
        )

_read_dir_configs

_read_dir_configs(conf_path, patterns, key, processed_files, read_environment_variables=False)

Discover, load, and dedupe raw config files from conf_path. No merge, no resolve.

Source code in kedro/config/omegaconf_config.py
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
@typing.no_type_check
def _read_dir_configs(
    self,
    conf_path: str,
    patterns: Iterable[str],
    key: str,
    processed_files: set,
    read_environment_variables: bool | None = False,
) -> dict[Path, DictConfig]:
    """Discover, load, and dedupe raw config files from ``conf_path``. No merge, no resolve."""
    # Handle directory existence check for remote paths
    if self._protocol in CLOUD_PROTOCOLS or self._protocol in HTTP_PROTOCOLS:
        try:
            # Check directory existence in remote paths
            self._fs.ls(conf_path)
        except Exception as exc:
            raise MissingConfigException(
                f"Given configuration path either does not exist "
                f"or is not a valid directory: {conf_path}. Error: {exc!s}"
            )
    # Original check for local paths
    elif not self._fs.isdir(Path(conf_path).as_posix()):
        raise MissingConfigException(
            f"Given configuration path either does not exist "
            f"or is not a valid directory: {conf_path}"
        )

    paths = []
    for pattern in patterns:
        for each in self._fs.glob(Path(f"{conf_path!s}/{pattern}").as_posix()):
            if not self.ignore_hidden or not self._is_hidden(each):
                paths.append(Path(each))

    deduplicated_paths = set(paths)
    config_files_filtered = [
        path for path in deduplicated_paths if self._is_valid_config_path(path)
    ]

    config_per_file = {}
    for config_filepath in config_files_filtered:
        try:
            with self._fs.open(str(config_filepath.as_posix())) as open_config:
                # As fsspec doesn't allow the file to be read as StringIO,
                # this is a workaround to read it as a binary file and decode it back to utf8.
                tmp_fo = io.StringIO(open_config.read().decode("utf8"))
                config = OmegaConf.load(tmp_fo)
                processed_files.add(config_filepath)
            if read_environment_variables:
                self._resolve_environment_variables(config)
            config_per_file[config_filepath] = config
        except (ParserError, ScannerError) as exc:
            line = exc.problem_mark.line
            cursor = exc.problem_mark.column
            raise ParserError(
                f"Invalid YAML or JSON file {Path(config_filepath).as_posix()},"
                f" unable to read line {line}, position {cursor}."
            ) from exc

    self._check_duplicates(key, config_per_file)
    return config_per_file

_read_raw_config

_read_raw_config(key)

Read raw per-file configs from base and env dirs for key.

Split out so callers can cache the parse and only re-pay the resolve cost per request.

Source code in kedro/config/omegaconf_config.py
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
def _read_raw_config(
    self, key: str
) -> tuple[dict[Path, DictConfig], dict[Path, DictConfig], str, str, set[Path]]:
    """Read raw per-file configs from base and env dirs for ``key``.

    Split out so callers can cache the parse and only re-pay the
    resolve cost per request.
    """
    patterns = [*self.config_patterns[key]]
    read_environment_variables = key == "credentials"

    processed_files: set[Path] = set()
    base_path = self._get_conf_env_path(self.base_env)
    base_configs = self._read_dir_configs(
        base_path, patterns, key, processed_files, read_environment_variables
    )

    run_env = self.env or self.default_run_env
    env_path = self._get_conf_env_path(run_env)
    if run_env == self.base_env:
        return base_configs, {}, base_path, env_path, processed_files

    env_configs = self._read_dir_configs(
        env_path, patterns, key, processed_files, read_environment_variables
    )
    return base_configs, env_configs, base_path, env_path, processed_files

_register_globals_resolver

_register_globals_resolver()

Register the globals resolver

Source code in kedro/config/omegaconf_config.py
635
636
637
638
639
640
641
def _register_globals_resolver(self) -> None:
    """Register the globals resolver"""
    OmegaConf.register_new_resolver(
        "globals",
        self._get_globals_value,
        replace=True,
    )

_register_new_resolvers staticmethod

_register_new_resolvers(resolvers)

Register custom resolvers

Source code in kedro/config/omegaconf_config.py
694
695
696
697
698
699
700
701
@staticmethod
def _register_new_resolvers(resolvers: dict[str, Callable]) -> None:
    """Register custom resolvers"""
    for name, resolver in resolvers.items():
        if not OmegaConf.has_resolver(name):
            msg = f"Registering new custom resolver: {name}"
            _config_logger.debug(msg)
            OmegaConf.register_new_resolver(name=name, resolver=resolver)

_register_runtime_params_resolver

_register_runtime_params_resolver()
Source code in kedro/config/omegaconf_config.py
643
644
645
646
647
648
def _register_runtime_params_resolver(self) -> None:
    OmegaConf.register_new_resolver(
        "runtime_params",
        self._get_runtime_value,
        replace=True,
    )

_resolve_catalog_config

_resolve_catalog_config(aggregate_config)

Resolve the catalog config, then guard against runtime_params: selecting a dataset's type -- see _guard_runtime_params_in_catalog_type.

Source code in kedro/config/omegaconf_config.py
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
@typing.no_type_check
def _resolve_catalog_config(
    self, aggregate_config: Iterable[DictConfig]
) -> dict[str, Any]:
    """Resolve the catalog config, then guard against `runtime_params:`
    selecting a dataset's `type` -- see `_guard_runtime_params_in_catalog_type`.
    """
    # Scope hit-tracking to this resolution pass only.
    self._runtime_params_hits = set()

    merged_config_container = OmegaConf.to_container(
        OmegaConf.unsafe_merge(*aggregate_config), resolve=True
    )
    self._guard_runtime_params_in_catalog_type(
        aggregate_config, merged_config_container
    )
    return merged_config_container

_resolve_environment_variables staticmethod

_resolve_environment_variables(config)

Use the oc.env resolver to read environment variables and replace them in-place, clearing the resolver after the operation is complete if it was not registered beforehand.

Parameters:

  • config (DictConfig) –

    The configuration dictionary to resolve.

Source code in kedro/config/omegaconf_config.py
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
@staticmethod
def _resolve_environment_variables(config: DictConfig) -> None:
    """Use the ``oc.env`` resolver to read environment variables and replace
    them in-place, clearing the resolver after the operation is complete if
    it was not registered beforehand.

    Args:
        config (DictConfig): The configuration dictionary to resolve.
    """
    if not OmegaConf.has_resolver("oc.env"):
        OmegaConf.register_new_resolver("oc.env", oc.env)
        OmegaConf.resolve(config)
        OmegaConf.clear_resolver("oc.env")
    else:
        OmegaConf.resolve(config)

_resolve_from_raw_config

_resolve_from_raw_config(key, base_configs, env_configs, base_path, env_path, processed_files)

Merge and resolve pre-parsed per-file configs into the final dict.

Uses self.runtime_params as currently set. Resolver registrations are process-global, so callers resolving per-request params must set them under a lock before calling.

Source code in kedro/config/omegaconf_config.py
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
def _resolve_from_raw_config(  # noqa: PLR0913
    self,
    key: str,
    base_configs: dict[Path, DictConfig],
    env_configs: dict[Path, DictConfig],
    base_path: str,
    env_path: str,
    processed_files: set[Path],
) -> dict[str, Any]:
    """Merge and resolve pre-parsed per-file configs into the final dict.

    Uses ``self.runtime_params`` as currently set. Resolver registrations
    are process-global, so callers resolving per-request params must set
    them under a lock before calling.
    """
    if key == "globals":
        # "runtime_params" resolver is not allowed in globals.
        OmegaConf.clear_resolver("runtime_params")

    try:
        base_config = self._merge_and_resolve(key, base_configs)
    except UnsupportedInterpolationType as exc:
        if "runtime_params" in str(exc):
            raise UnsupportedInterpolationType(
                "The `runtime_params:` resolver is not supported for globals."
            )
        raise

    run_env = self.env or self.default_run_env
    if run_env == self.base_env:
        return base_config  # type: ignore[no-any-return]

    try:
        env_config = self._merge_and_resolve(key, env_configs)
    except UnsupportedInterpolationType as exc:
        if "runtime_params" in str(exc):
            raise UnsupportedInterpolationType(
                "The `runtime_params:` resolver is not supported for globals."
            )
        raise

    resulting_config = self._merge_configs(base_config, env_config, key, env_path)

    if not processed_files and key != "globals":
        raise MissingConfigException(
            f"No files of YAML or JSON format found in {base_path} or {env_path} matching"
            f" the glob pattern(s): {[*self.config_patterns[key]]}"
        )

    return resulting_config  # type: ignore[no-any-return]

_soft_merge staticmethod

_soft_merge(config, env_config, env_path=None)
Source code in kedro/config/omegaconf_config.py
772
773
774
775
776
777
@staticmethod
def _soft_merge(
    config: dict[str, Any], env_config: dict[str, Any], env_path: str | None = None
) -> Any:
    # Soft merge the two env dirs. The chosen env will override base if keys clash.
    return OmegaConf.to_container(OmegaConf.unsafe_merge(config, env_config))

keys

keys()
Source code in kedro/config/omegaconf_config.py
337
338
def keys(self) -> KeysView:
    return KeysView(self.config_patterns)

load_and_merge_dir_config

load_and_merge_dir_config(conf_path, patterns, key, processed_files, read_environment_variables=False)

Recursively load and merge all configuration files in a directory using OmegaConf, which satisfy a given list of glob patterns from a specific path.

Parameters:

  • conf_path (str) –

    Path to configuration directory.

  • patterns (Iterable[str]) –

    List of glob patterns to match the filenames against.

  • key (str) –

    Key of the configuration type to fetch.

  • processed_files (set) –

    Set of files read for a given configuration type.

  • read_environment_variables (bool | None, default: False ) –

    Whether to resolve environment variables.

Raises:

  • MissingConfigException –

    If configuration path doesn't exist or isn't valid.

  • ValueError –

    If two or more configuration files contain the same key(s).

  • ParserError –

    If config file contains invalid YAML or JSON syntax.

Returns:

  • dict[str, Any] –

    Resulting configuration dictionary.

Source code in kedro/config/omegaconf_config.py
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
@typing.no_type_check
def load_and_merge_dir_config(
    self,
    conf_path: str,
    patterns: Iterable[str],
    key: str,
    processed_files: set,
    read_environment_variables: bool | None = False,
) -> dict[str, Any]:
    """Recursively load and merge all configuration files in a directory using OmegaConf,
    which satisfy a given list of glob patterns from a specific path.

    Args:
        conf_path: Path to configuration directory.
        patterns: List of glob patterns to match the filenames against.
        key: Key of the configuration type to fetch.
        processed_files: Set of files read for a given configuration type.
        read_environment_variables: Whether to resolve environment variables.

    Raises:
        MissingConfigException: If configuration path doesn't exist or isn't valid.
        ValueError: If two or more configuration files contain the same key(s).
        ParserError: If config file contains invalid YAML or JSON syntax.

    Returns:
        Resulting configuration dictionary.

    """
    config_per_file = self._read_dir_configs(
        conf_path, patterns, key, processed_files, read_environment_variables
    )
    return self._merge_and_resolve(key, config_per_file)