콘텐츠로 이동

Core API Reference

creator

Manages dynamic creation of agents and components based on configuration.

Creator

Central factory for creating agents and components from configuration.

This class dynamically instantiates all necessary objects for an experiment based on a configuration file, following a convention-over-configuration approach.

Key Responsibilities: - Creates the main Agent for the experiment. - Provides a ComponentCreator to the agent, enabling it to build other components like models, optimizers, and data loaders.

Usage Example

cfg = Config("config.yaml") create = Creator(cfg) agent = create.agent() # Agent can now use create.* to build its parts.

Source code in cvlabkit/core/creator.py
class Creator:
    """Central factory for creating agents and components from configuration.

    This class dynamically instantiates all necessary objects for an experiment
    based on a configuration file, following a convention-over-configuration approach.

    Key Responsibilities:
    - Creates the main `Agent` for the experiment.
    - Provides a `ComponentCreator` to the agent, enabling it to build
      other components like models, optimizers, and data loaders.

    Usage Example:
        cfg = Config("config.yaml")
        create = Creator(cfg)
        agent = create.agent() # Agent can now use create.* to build its parts.
    """

    def __init__(self, cfg: Config):
        """Initializes the Creator with the main configuration.

        Args:
            cfg: The configuration object driving the creation process.
        """
        self.cfg = cfg
        self.component_creator = ComponentCreator(cfg)

    def __getattr__(self, category: str) -> Any:
        """Provides access to the appropriate loader for a given component category.

        This method is the entry point for creating any component:
        - `create.agent()` is handled by `_AgentLoader`.
        - `create.model()`, `create.optimizer()`, etc., are delegated to `ComponentCreator`.

        Args:
            category: The component category name (e.g., 'agent', 'model').

        Returns:
            A loader object capable of creating instances for that category.
        """
        if category == "agent":
            # Agents are special. They get their own loader which injects the
            # component_creator, allowing the agent to build its own components.
            return _AgentLoader(self.cfg, self.component_creator)
        else:
            # All other component types are handled by the ComponentCreator.
            return getattr(self.component_creator, category)

__init__

Initializes the Creator with the main configuration.

Parameters:

Name Type Description Default
cfg Config

The configuration object driving the creation process.

required
Source code in cvlabkit/core/creator.py
def __init__(self, cfg: Config):
    """Initializes the Creator with the main configuration.

    Args:
        cfg: The configuration object driving the creation process.
    """
    self.cfg = cfg
    self.component_creator = ComponentCreator(cfg)

__getattr__

Provides access to the appropriate loader for a given component category.

This method is the entry point for creating any component: - create.agent() is handled by _AgentLoader. - create.model(), create.optimizer(), etc., are delegated to ComponentCreator.

Parameters:

Name Type Description Default
category str

The component category name (e.g., 'agent', 'model').

required

Returns:

Type Description
Any

A loader object capable of creating instances for that category.

Source code in cvlabkit/core/creator.py
def __getattr__(self, category: str) -> Any:
    """Provides access to the appropriate loader for a given component category.

    This method is the entry point for creating any component:
    - `create.agent()` is handled by `_AgentLoader`.
    - `create.model()`, `create.optimizer()`, etc., are delegated to `ComponentCreator`.

    Args:
        category: The component category name (e.g., 'agent', 'model').

    Returns:
        A loader object capable of creating instances for that category.
    """
    if category == "agent":
        # Agents are special. They get their own loader which injects the
        # component_creator, allowing the agent to build its own components.
        return _AgentLoader(self.cfg, self.component_creator)
    else:
        # All other component types are handled by the ComponentCreator.
        return getattr(self.component_creator, category)

ComponentCreator

Creator Implementation for creating non-agent specific components (e.g., model, optimizer, dataset, etc.).

This class discovers and instantiates components from the cvlabkit.component package based on the provided configuration.

Source code in cvlabkit/core/creator.py
class ComponentCreator:
    """Creator Implementation for creating non-agent specific components
    (e.g., model, optimizer, dataset, etc.).

    This class discovers and instantiates components from the `cvlabkit.component`
    package based on the provided configuration.
    """

    def __init__(self, cfg: Config):
        """Initializes the ComponentCreator.

        Args:
            cfg: The main configuration object.
        """
        self.cfg = cfg
        self._base_classes = self._get_all_base_classes()

    def _get_all_base_classes(self) -> Dict[str, type]:
        """Finds and returns all component base classes from `cvlabkit.component.base`.

        This method inspects the `base` module and creates a mapping from a
        category name (e.g., "model") to its base class (e.g., `Model`).
        This avoids hardcoding the relationship between categories and their
        base classes.
        """
        base_classes = {}
        for name, cls in inspect.getmembers(component_base, inspect.isclass):
            if hasattr(cls, "__module__") and cls.__module__.startswith(
                "cvlabkit.component.base"
            ):
                base_classes[name.lower()] = cls
        return base_classes

    # TODO: Add support for recursive agent creation, where agents can create other agents.
    def __getattr__(self, category: str) -> "_ComponentCategoryLoader":
        """Returns a specialized loader for a specific component category.

        This method is called when `create.model` or `create.optimizer` is accessed.

        Args:
            category: The component category name (e.g., 'model', 'optimizer').

        Returns:
            A loader instance for the specified category.

        Raises:
            AttributeError: If the category name is 'agent',
                            or if no base class is found for the category.
        """
        if category == "agent":
            raise AttributeError(
                f"'{type(self).__name__}' can not support 'agent' category."
            )

        base_class = self._base_classes.get(category)
        if base_class is None:
            raise AttributeError(
                f"No base class found for component category '{category}'."
            )

        return _ComponentCategoryLoader(self.cfg, category, base_class)

__init__

Initializes the ComponentCreator.

Parameters:

Name Type Description Default
cfg Config

The main configuration object.

required
Source code in cvlabkit/core/creator.py
def __init__(self, cfg: Config):
    """Initializes the ComponentCreator.

    Args:
        cfg: The main configuration object.
    """
    self.cfg = cfg
    self._base_classes = self._get_all_base_classes()

__getattr__

Returns a specialized loader for a specific component category.

This method is called when create.model or create.optimizer is accessed.

Parameters:

Name Type Description Default
category str

The component category name (e.g., 'model', 'optimizer').

required

Returns:

Type Description
_ComponentCategoryLoader

A loader instance for the specified category.

Raises:

Type Description
AttributeError

If the category name is 'agent', or if no base class is found for the category.

Source code in cvlabkit/core/creator.py
def __getattr__(self, category: str) -> "_ComponentCategoryLoader":
    """Returns a specialized loader for a specific component category.

    This method is called when `create.model` or `create.optimizer` is accessed.

    Args:
        category: The component category name (e.g., 'model', 'optimizer').

    Returns:
        A loader instance for the specified category.

    Raises:
        AttributeError: If the category name is 'agent',
                        or if no base class is found for the category.
    """
    if category == "agent":
        raise AttributeError(
            f"'{type(self).__name__}' can not support 'agent' category."
        )

    base_class = self._base_classes.get(category)
    if base_class is None:
        raise AttributeError(
            f"No base class found for component category '{category}'."
        )

    return _ComponentCategoryLoader(self.cfg, category, base_class)

config

This module defines the Config class, a central component for managing experiment configurations within the cvlab-kit framework.

It provides functionalities for loading configurations from YAML files or dictionaries, accessing parameters using both dictionary-style and attribute-style syntax, merging configurations, and expanding configurations for grid search experiments. The Config class also integrates with ConfigProxy for config validation during dry runs.

Config

A configuration class that handles loading, accessing, and manipulating parameters.

This class provides a unified interface for managing experiment configurations. It can load settings from a YAML file or a dictionary, provides attribute-style access to parameters, and supports advanced features like grid search expansion and special syntax parsing for component-specific parameters.

Attributes:

Name Type Description
_data dict[str, Any]

The internal dictionary storing the configuration parameters.

proxy ConfigProxy

An object that tracks missing configuration keys during a dry run to help generate a template. It is primarily used by the __getattr__ method when a requested key is not found.

Source code in cvlabkit/core/config.py
class Config:
    """A configuration class that handles loading, accessing, and manipulating parameters.

    This class provides a unified interface for managing experiment configurations.
    It can load settings from a YAML file or a dictionary, provides attribute-style
    access to parameters, and supports advanced features like grid search expansion
    and special syntax parsing for component-specific parameters.

    Attributes:
        _data (Dict[str, Any]): The internal dictionary storing the configuration
            parameters.
        proxy (ConfigProxy): An object that tracks missing configuration keys
            during a dry run to help generate a template. It is primarily used
            by the `__getattr__` method when a requested key is not found.
    """

    def __init__(
        self, source: Union[str, Dict[str, Any]], proxy: ConfigProxy = None
    ) -> None:
        """Initializes the Config object.

        Args:
            source: A file path to a YAML file (str) or a dictionary containing
                the configuration parameters (Dict[str, Any]).
            proxy: An optional `ConfigProxy` instance for advanced scenarios like
                generating configuration templates during dry runs. If not provided,
                a new `ConfigProxy` will be initialized.

        Raises:
            TypeError: If the source is not a file path (str) or a dictionary.
        """
        if isinstance(source, str):
            # Load configuration from a YAML file.
            with open(source) as f:
                self._data = yaml.load(f, Loader=yaml.FullLoader)
        elif isinstance(source, dict):
            # Use the provided dictionary directly, creating a deep copy to avoid
            # external modifications.
            self._data = deepcopy(source)
        else:
            # Raise an error for unsupported source types.
            raise TypeError("Config expects a YAML file path (str) or a dictionary.")

        # Initialize the ConfigProxy. This proxy is used for advanced scenarios
        # like generating configuration templates during a dry run.
        self.proxy = proxy if proxy else ConfigProxy(self)

    def __getitem__(self, key: str) -> Any:
        """Enables dictionary-style access to configuration parameters.

        Allows accessing configuration values using bracket notation, e.g., `config["model.name"]`.

        Args:
            key (str): The dot-separated string representing the nested key
                (e.g., "model.params.lr").

        Returns:
            Any: The configuration value associated with the key.
        """
        return self.get(key)

    def get(self, key: str, default: Optional[Any] = None) -> Any:
        """Retrieves a value using a dot-separated key.

        This method allows accessing nested configuration values using a single
        dot-separated string. For example, `config.get("model.params.lr")`.

        Args:
            key (str): A dot-separated string representing the nested key
                (e.g., "model.params.lr").
            default (Optional[Any]): The value to return if the key is not found.
                Defaults to `None`.

        Returns:
            Any: The configuration value associated with the key, or the
                specified `default` value if the key is not found.
        """
        keys = key.split(".")
        current_level = self._data
        # Traverse the nested dictionary using the split keys.
        for k in keys:
            # Check if the current level is a dictionary and contains the key.
            if isinstance(current_level, dict) and k in current_level:
                current_level = current_level[k]
            else:
                # If any part of the key path is not found, return the default.
                return default
        return current_level

    def __getattr__(self, key: str) -> Any:
        """Enables attribute-style access to configuration parameters.

        Allows accessing configuration values using dot notation, e.g., `config.model.name`.
        If the accessed attribute is a dictionary, it is wrapped in a new
        `Config` object to allow for nested attribute access.

        Args:
            key (str): The name of the attribute to access.

        Returns:
            Any: The configuration value, or a new `Config` object for nested
                dictionaries, or a `Placeholder` if the key is missing and the
                proxy is active.

        Raises:
            AttributeError: If the attribute is not a configuration key and not
                a standard Python attribute.
        """
        # Check if the key exists in the internal data.
        if key in self._data:
            value = self._data[key]
            # If the value is a dictionary, wrap it in a new Config object
            # to enable further attribute-style access.
            return Config(value, proxy=self.proxy) if isinstance(value, dict) else value

        # If the key is not found in _data, try to get it as a regular attribute
        # of the Config object itself (e.g., `proxy`).
        try:
            return object.__getattribute__(self, key)
        except AttributeError:
            # If it's not a regular attribute, then it must be a missing config key.
            # Delegate to the proxy. This is used in dry-run scenarios to track missing keys.
            if self.proxy.active:
                return self.proxy.resolve_missing(key)
            # If the proxy is not active, raise an AttributeError.
            raise AttributeError(
                f"Config has no attribute '{key}' and fast mode is not active."
            )

    def __contains__(self, key: str) -> bool:
        """Checks if a key exists in the configuration.

        Args:
            key (str): The key to check.

        Returns:
            bool: True if the key exists, False otherwise.
        """
        return key in self._data

    def to_dict(self) -> Dict[str, Any]:
        """Returns a deep copy of the internal configuration data as a dictionary.

        This is useful for obtaining a mutable copy of the configuration that
        can be modified without affecting the original `Config` object.

        Returns:
            Dict[str, Any]: A deep copy of the configuration data.
        """
        return deepcopy(self._data)

    def merge(self, new_params: Dict[str, Any]) -> "Config":
        """Merges a dictionary of new parameters into the current configuration.

        This method creates a new `Config` object by combining the current
        configuration with the provided `new_params`. Parameters in `new_params`
        will overwrite existing ones at the top level.

        Args:
            new_params (Dict[str, Any]): A dictionary of parameters to merge.

        Returns:
            Config: A new `Config` object with the merged parameters.
        """
        merged_data = deepcopy(self._data)
        # Update the merged data with new parameters. Note that this is a shallow
        # merge at the top level; nested dictionaries are replaced, not merged.
        merged_data.update(new_params)
        return Config(merged_data, proxy=self.proxy)

    def expand(self) -> List["Config"]:
        """Expands the configuration into multiple `Config` objects for a grid search.

        This method identifies all parameters in the configuration that are lists
        and creates a Cartesian product of their values. Each combination results
        in a new `Config` object, effectively generating all configurations for
        a grid search experiment.

        Returns:
            List[Config]: A list of `Config` objects, one for each parameter
                combination. If no list-based parameters are found, it returns
                a list containing only the original `Config` object.
        """
        # Importing product from itertools to generate combinations.
        from itertools import product

        # Flatten the nested configuration into a single-level dictionary.
        flat_config = self._flatten(self._data)
        # Identify keys whose values are lists, indicating parameters for grid search.
        grid_keys = [k for k, v in flat_config.items() if isinstance(v, list)]

        # If no list-based parameters are found, return the original config in a list.
        if not grid_keys:
            return [self]

        # Get the values for each grid key to form combinations.
        combinations = [flat_config[k] for k in grid_keys]
        expanded_configs = []
        # Generate all possible combinations using `itertools.product`.
        for combo in product(*combinations):
            flat_instance = deepcopy(flat_config)
            # Apply the current combination of values to the flattened config.
            for key, value in zip(grid_keys, combo):
                flat_instance[key] = value
            # Unflatten the modified dictionary back into a nested structure.
            nested_config = self._unflatten(flat_instance)
            # Create a new Config object for each combination.
            expanded_configs.append(Config(nested_config, proxy=self.proxy))
        return expanded_configs

    @staticmethod
    def _flatten(
        d: Dict[str, Any], parent_key: str = "", sep: str = "."
    ) -> Dict[str, Any]:
        """Flattens a nested dictionary into a single-level dictionary.

        Keys are concatenated using the specified separator (default: '.').

        Args:
            d (Dict[str, Any]): The dictionary to flatten.
            parent_key (str): The base key for the current level of recursion.
                Defaults to an empty string.
            sep (str): The separator to use for concatenating keys. Defaults to '.'.

        Returns:
            Dict[str, Any]: A flattened dictionary where keys represent the full
                path to the original nested values.
        """
        items = {}
        for k, v in d.items():
            new_key = f"{parent_key}{sep}{k}" if parent_key else k
            # Recursively flatten if the value is a dictionary.
            if isinstance(v, dict):
                items.update(Config._flatten(v, new_key, sep=sep))
            else:
                # Otherwise, add the key-value pair to the flattened items.
                items[new_key] = v
        return items

    @staticmethod
    def _unflatten(flat_dict: Dict[str, Any], sep: str = ".") -> Dict[str, Any]:
        """Converts a flattened dictionary back into a nested dictionary.

        Args:
            flat_dict (Dict[str, Any]): The flattened dictionary to unflatten.
            sep (str): The separator used to concatenate keys during flattening.
                Defaults to '.'.

        Returns:
            result (Dict[str, Any]): A nested dictionary reconstructed from
                the flattened one.
        """
        # Initialize an empty dictionary to hold the nested structure.
        result = {}
        for k, v in flat_dict.items():
            # Split the flattened key into its component parts.
            keys = k.split(sep)
            d = result
            # Traverse or create nested dictionaries until the last key.
            for part in keys[:-1]:
                if part not in d:
                    d[part] = {}
                d = d[part]
            # Assign the value to the innermost key.
            d[keys[-1]] = v
        return result

    def dump_template(self, file_path: str) -> None:
        """Dumps the current configuration, including inferred missing keys, to a YAML file.

        This method is primarily used during dry runs to generate a template
        configuration file that includes all parameters accessed by the system,
        even those not explicitly defined in the initial config.

        Args:
            file_path (str): The path to the YAML file where the template will be saved.
        """
        # Merge the initial data with the missing keys resolved by the proxy.
        # The proxy's `missing` dictionary contains the inferred default values.
        template_data = deepcopy(self._data)
        for key, value in self.proxy.missing.items():
            # Convert dot-separated key back to nested dictionary structure
            keys = key.split(".")
            current_level = template_data
            for i, k in enumerate(keys):
                if i == len(keys) - 1:  # Last key
                    current_level[k] = value
                else:
                    if k not in current_level or not isinstance(current_level[k], dict):
                        current_level[k] = {}
                    current_level = current_level[k]

        with open(file_path, "w") as f:
            yaml.dump(template_data, f, default_flow_style=False, sort_keys=False)

    def __getstate__(self):
        """Returns the state of the Config object for pickling/deepcopying."""
        return {"_data": self._data, "proxy": self.proxy}

    def __setstate__(self, state):
        """Restores the state of the Config object from pickling/deepcopying."""
        self._data = state["_data"]
        self.proxy = state["proxy"]

__init__

Initializes the Config object.

Parameters:

Name Type Description Default
source str | dict[str, Any]

A file path to a YAML file (str) or a dictionary containing the configuration parameters (Dict[str, Any]).

required
proxy ConfigProxy

An optional ConfigProxy instance for advanced scenarios like generating configuration templates during dry runs. If not provided, a new ConfigProxy will be initialized.

None

Raises:

Type Description
TypeError

If the source is not a file path (str) or a dictionary.

Source code in cvlabkit/core/config.py
def __init__(
    self, source: Union[str, Dict[str, Any]], proxy: ConfigProxy = None
) -> None:
    """Initializes the Config object.

    Args:
        source: A file path to a YAML file (str) or a dictionary containing
            the configuration parameters (Dict[str, Any]).
        proxy: An optional `ConfigProxy` instance for advanced scenarios like
            generating configuration templates during dry runs. If not provided,
            a new `ConfigProxy` will be initialized.

    Raises:
        TypeError: If the source is not a file path (str) or a dictionary.
    """
    if isinstance(source, str):
        # Load configuration from a YAML file.
        with open(source) as f:
            self._data = yaml.load(f, Loader=yaml.FullLoader)
    elif isinstance(source, dict):
        # Use the provided dictionary directly, creating a deep copy to avoid
        # external modifications.
        self._data = deepcopy(source)
    else:
        # Raise an error for unsupported source types.
        raise TypeError("Config expects a YAML file path (str) or a dictionary.")

    # Initialize the ConfigProxy. This proxy is used for advanced scenarios
    # like generating configuration templates during a dry run.
    self.proxy = proxy if proxy else ConfigProxy(self)

__getitem__

Enables dictionary-style access to configuration parameters.

Allows accessing configuration values using bracket notation, e.g., config["model.name"].

Parameters:

Name Type Description Default
key str

The dot-separated string representing the nested key (e.g., "model.params.lr").

required

Returns:

Name Type Description
Any Any

The configuration value associated with the key.

Source code in cvlabkit/core/config.py
def __getitem__(self, key: str) -> Any:
    """Enables dictionary-style access to configuration parameters.

    Allows accessing configuration values using bracket notation, e.g., `config["model.name"]`.

    Args:
        key (str): The dot-separated string representing the nested key
            (e.g., "model.params.lr").

    Returns:
        Any: The configuration value associated with the key.
    """
    return self.get(key)

get

Retrieves a value using a dot-separated key.

This method allows accessing nested configuration values using a single dot-separated string. For example, config.get("model.params.lr").

Parameters:

Name Type Description Default
key str

A dot-separated string representing the nested key (e.g., "model.params.lr").

required
default Any | None

The value to return if the key is not found. Defaults to None.

None

Returns:

Name Type Description
Any Any

The configuration value associated with the key, or the specified default value if the key is not found.

Source code in cvlabkit/core/config.py
def get(self, key: str, default: Optional[Any] = None) -> Any:
    """Retrieves a value using a dot-separated key.

    This method allows accessing nested configuration values using a single
    dot-separated string. For example, `config.get("model.params.lr")`.

    Args:
        key (str): A dot-separated string representing the nested key
            (e.g., "model.params.lr").
        default (Optional[Any]): The value to return if the key is not found.
            Defaults to `None`.

    Returns:
        Any: The configuration value associated with the key, or the
            specified `default` value if the key is not found.
    """
    keys = key.split(".")
    current_level = self._data
    # Traverse the nested dictionary using the split keys.
    for k in keys:
        # Check if the current level is a dictionary and contains the key.
        if isinstance(current_level, dict) and k in current_level:
            current_level = current_level[k]
        else:
            # If any part of the key path is not found, return the default.
            return default
    return current_level

__getattr__

Enables attribute-style access to configuration parameters.

Allows accessing configuration values using dot notation, e.g., config.model.name. If the accessed attribute is a dictionary, it is wrapped in a new Config object to allow for nested attribute access.

Parameters:

Name Type Description Default
key str

The name of the attribute to access.

required

Returns:

Name Type Description
Any Any

The configuration value, or a new Config object for nested dictionaries, or a Placeholder if the key is missing and the proxy is active.

Raises:

Type Description
AttributeError

If the attribute is not a configuration key and not a standard Python attribute.

Source code in cvlabkit/core/config.py
def __getattr__(self, key: str) -> Any:
    """Enables attribute-style access to configuration parameters.

    Allows accessing configuration values using dot notation, e.g., `config.model.name`.
    If the accessed attribute is a dictionary, it is wrapped in a new
    `Config` object to allow for nested attribute access.

    Args:
        key (str): The name of the attribute to access.

    Returns:
        Any: The configuration value, or a new `Config` object for nested
            dictionaries, or a `Placeholder` if the key is missing and the
            proxy is active.

    Raises:
        AttributeError: If the attribute is not a configuration key and not
            a standard Python attribute.
    """
    # Check if the key exists in the internal data.
    if key in self._data:
        value = self._data[key]
        # If the value is a dictionary, wrap it in a new Config object
        # to enable further attribute-style access.
        return Config(value, proxy=self.proxy) if isinstance(value, dict) else value

    # If the key is not found in _data, try to get it as a regular attribute
    # of the Config object itself (e.g., `proxy`).
    try:
        return object.__getattribute__(self, key)
    except AttributeError:
        # If it's not a regular attribute, then it must be a missing config key.
        # Delegate to the proxy. This is used in dry-run scenarios to track missing keys.
        if self.proxy.active:
            return self.proxy.resolve_missing(key)
        # If the proxy is not active, raise an AttributeError.
        raise AttributeError(
            f"Config has no attribute '{key}' and fast mode is not active."
        )

__contains__

Checks if a key exists in the configuration.

Parameters:

Name Type Description Default
key str

The key to check.

required

Returns:

Name Type Description
bool bool

True if the key exists, False otherwise.

Source code in cvlabkit/core/config.py
def __contains__(self, key: str) -> bool:
    """Checks if a key exists in the configuration.

    Args:
        key (str): The key to check.

    Returns:
        bool: True if the key exists, False otherwise.
    """
    return key in self._data

to_dict

Returns a deep copy of the internal configuration data as a dictionary.

This is useful for obtaining a mutable copy of the configuration that can be modified without affecting the original Config object.

Returns:

Type Description
dict[str, Any]

Dict[str, Any]: A deep copy of the configuration data.

Source code in cvlabkit/core/config.py
def to_dict(self) -> Dict[str, Any]:
    """Returns a deep copy of the internal configuration data as a dictionary.

    This is useful for obtaining a mutable copy of the configuration that
    can be modified without affecting the original `Config` object.

    Returns:
        Dict[str, Any]: A deep copy of the configuration data.
    """
    return deepcopy(self._data)

merge

Merges a dictionary of new parameters into the current configuration.

This method creates a new Config object by combining the current configuration with the provided new_params. Parameters in new_params will overwrite existing ones at the top level.

Parameters:

Name Type Description Default
new_params dict[str, Any]

A dictionary of parameters to merge.

required

Returns:

Name Type Description
Config Config

A new Config object with the merged parameters.

Source code in cvlabkit/core/config.py
def merge(self, new_params: Dict[str, Any]) -> "Config":
    """Merges a dictionary of new parameters into the current configuration.

    This method creates a new `Config` object by combining the current
    configuration with the provided `new_params`. Parameters in `new_params`
    will overwrite existing ones at the top level.

    Args:
        new_params (Dict[str, Any]): A dictionary of parameters to merge.

    Returns:
        Config: A new `Config` object with the merged parameters.
    """
    merged_data = deepcopy(self._data)
    # Update the merged data with new parameters. Note that this is a shallow
    # merge at the top level; nested dictionaries are replaced, not merged.
    merged_data.update(new_params)
    return Config(merged_data, proxy=self.proxy)

expand

Expands the configuration into multiple Config objects for a grid search.

This method identifies all parameters in the configuration that are lists and creates a Cartesian product of their values. Each combination results in a new Config object, effectively generating all configurations for a grid search experiment.

Returns:

Type Description
list[Config]

List[Config]: A list of Config objects, one for each parameter combination. If no list-based parameters are found, it returns a list containing only the original Config object.

Source code in cvlabkit/core/config.py
def expand(self) -> List["Config"]:
    """Expands the configuration into multiple `Config` objects for a grid search.

    This method identifies all parameters in the configuration that are lists
    and creates a Cartesian product of their values. Each combination results
    in a new `Config` object, effectively generating all configurations for
    a grid search experiment.

    Returns:
        List[Config]: A list of `Config` objects, one for each parameter
            combination. If no list-based parameters are found, it returns
            a list containing only the original `Config` object.
    """
    # Importing product from itertools to generate combinations.
    from itertools import product

    # Flatten the nested configuration into a single-level dictionary.
    flat_config = self._flatten(self._data)
    # Identify keys whose values are lists, indicating parameters for grid search.
    grid_keys = [k for k, v in flat_config.items() if isinstance(v, list)]

    # If no list-based parameters are found, return the original config in a list.
    if not grid_keys:
        return [self]

    # Get the values for each grid key to form combinations.
    combinations = [flat_config[k] for k in grid_keys]
    expanded_configs = []
    # Generate all possible combinations using `itertools.product`.
    for combo in product(*combinations):
        flat_instance = deepcopy(flat_config)
        # Apply the current combination of values to the flattened config.
        for key, value in zip(grid_keys, combo):
            flat_instance[key] = value
        # Unflatten the modified dictionary back into a nested structure.
        nested_config = self._unflatten(flat_instance)
        # Create a new Config object for each combination.
        expanded_configs.append(Config(nested_config, proxy=self.proxy))
    return expanded_configs

dump_template

Dumps the current configuration, including inferred missing keys, to a YAML file.

This method is primarily used during dry runs to generate a template configuration file that includes all parameters accessed by the system, even those not explicitly defined in the initial config.

Parameters:

Name Type Description Default
file_path str

The path to the YAML file where the template will be saved.

required
Source code in cvlabkit/core/config.py
def dump_template(self, file_path: str) -> None:
    """Dumps the current configuration, including inferred missing keys, to a YAML file.

    This method is primarily used during dry runs to generate a template
    configuration file that includes all parameters accessed by the system,
    even those not explicitly defined in the initial config.

    Args:
        file_path (str): The path to the YAML file where the template will be saved.
    """
    # Merge the initial data with the missing keys resolved by the proxy.
    # The proxy's `missing` dictionary contains the inferred default values.
    template_data = deepcopy(self._data)
    for key, value in self.proxy.missing.items():
        # Convert dot-separated key back to nested dictionary structure
        keys = key.split(".")
        current_level = template_data
        for i, k in enumerate(keys):
            if i == len(keys) - 1:  # Last key
                current_level[k] = value
            else:
                if k not in current_level or not isinstance(current_level[k], dict):
                    current_level[k] = {}
                current_level = current_level[k]

    with open(file_path, "w") as f:
        yaml.dump(template_data, f, default_flow_style=False, sort_keys=False)

__getstate__

Returns the state of the Config object for pickling/deepcopying.

Source code in cvlabkit/core/config.py
def __getstate__(self):
    """Returns the state of the Config object for pickling/deepcopying."""
    return {"_data": self._data, "proxy": self.proxy}

__setstate__

Restores the state of the Config object from pickling/deepcopying.

Source code in cvlabkit/core/config.py
def __setstate__(self, state):
    """Restores the state of the Config object from pickling/deepcopying."""
    self._data = state["_data"]
    self.proxy = state["proxy"]