Source code for netloader.utils.configs

"""
Configuration dataclasses for the network and layers.
"""
import logging as log
from dataclasses import dataclass, field
from typing import TypeVar, Any, Literal, get_type_hints

from netloader import layers


ConfigT = TypeVar('ConfigT', bound='BaseConfig')


[docs] @dataclass class BaseConfig: """ Base dataclass to hold configuration parameters. """ _changed_fields: set[str] = field(default_factory=set, init=False, repr=False) def __getattr__(self, item: str) -> Any: """ Gets an attribute of the dataclass. Parameters ---------- item : str Key of the attribute Returns ------- Any Value of the attribute """ item = item.replace('-', '_') return super().__getattribute__(item) def __setattr__(self, key: str, value: Any) -> None: """ Sets an attribute of the dataclass. Parameters ---------- key : str Key of the attribute value : Any Value to set """ if key.startswith('_'): return super().__setattr__(key, value) key = key.replace('-', '_') hint: type = get_type_hints(type(self))[key] if getattr(hint, '__origin__', None) is Literal: self._check_literal(key, value, getattr(hint, '__args__', ())) if value != self.__dataclass_fields__[key].default: self._changed_fields.add(key) return super().__setattr__(key, value) @staticmethod def _check_literal(key: str, value: Any, options: tuple[Any, ...]) -> None: """ Checks if a value is in a list of options. Parameters ---------- key : str Key of the value value : Any Value to check options : list[Any] List of options """ if value not in options: raise ValueError(f'Invalid value for {key} ({value}), must be one of {options}')
[docs] @classmethod def from_dict(cls: type[ConfigT], config: dict[str, Any], new_fields: bool = False) -> ConfigT: """ Creates a BaseConfig object from a dictionary. Parameters ---------- config : dict[str, Any] Configuration dictionary new_fields : bool, Optional Whether to allow new fields in the dictionary, default = False Returns ------- ConfigT Configuration dataclass """ instance: ConfigT = type.__call__(cls) instance.merge_dict(config, new_fields=new_fields) return instance
[docs] def get_changed_fields(self) -> set[str]: """ Gets the set of changed fields in the dataclass. Returns ------- set[str] Set of changed fields """ return self._changed_fields
[docs] def merge(self: ConfigT, other: ConfigT, new_fields: bool = False) -> None: """ Merges another dataclass into this one. Parameters ---------- other : ConfigT Configuration dataclass to merge new_fields : bool, Optional Whether to allow new fields in the other dataclass, default = False """ key: str for key in other.get_changed_fields(): if not hasattr(self, key) and not new_fields: raise AttributeError(f'Unknown config key ({key}) and new_fields is False') if hasattr(self, key) and isinstance(getattr(self, key), BaseConfig): getattr(self, key).merge(getattr(other, key), new_fields=new_fields) else: setattr(self, key, getattr(other, key))
[docs] def merge_dict(self, other: dict[str, Any], new_fields: bool = False) -> None: """ Merges a dictionary into this dataclass. Parameters ---------- other : dict[str, Any] Configuration dictionary to merge new_fields : bool, Optional Whether to allow new fields in the dictionary, default = False """ key: str value: Any for key, value in other.items(): key.replace('-', '_') if not hasattr(self, key) and not new_fields: raise AttributeError(f'Unknown config key ({key}) and new_fields is False') if hasattr(self, key) and isinstance(getattr(self, key), BaseConfig): getattr(self, key).merge_dict(value, new_fields=new_fields) else: setattr(self, key, value)
[docs] def to_dict(self) -> dict[str, Any]: """ Converts the dataclass to a dictionary. Returns ------- dict[str, Any] Configuration dictionary """ key: str result: dict[str, Any] = {} value: Any for key, value in self.__dict__.items(): if key.startswith('_'): continue if isinstance(value, BaseConfig): result[key] = value.to_dict() else: result[key] = value return result
[docs] @dataclass class NetConfig(BaseConfig): """ Dataclass to hold global network configuration parameters. Attributes ---------- checkpoints : bool Whether to capture intermediate outputs as checkpoints paper : str Reference to the paper describing the network github : str Link to the GitHub repository for the network description : str Description of the network layers : dict[str, Any] Dictionary of default layer configurations """ checkpoints: bool = False paper: str = '' github: str = '' description: str = '' layers: dict[str, Any] = field(default_factory=dict)
[docs] def merge_dict(self, other: dict[str, Any], new_fields: bool = False) -> None: key: str value: Any if 'layers' in other: self.layers.update(other.pop('layers')) for key, value in other.items(): if hasattr(layers, key) or key == 'Composite': self.layers[key] = value elif not hasattr(self, key) and not new_fields: raise AttributeError(f'Unknown net config key ({key}) and new_fields is False') elif hasattr(NetConfig, key) and isinstance(getattr(self, key), BaseConfig): getattr(self, key).merge_dict(value, new_fields=new_fields) elif hasattr(NetConfig, key): setattr(self, key, value) else: log.getLogger(__name__).warning(f'Unknown net config key ({key})')
[docs] @dataclass class Config(BaseConfig): """ Dataclass to hold global network and layer configuration parameters. Attributes ---------- layers : list[dict[str, Any]] List of layers net : NetConfig Network configuration """ layers: list[dict[str, Any]] = field(default_factory=list) net: NetConfig = field(default_factory=NetConfig)
__all__ = ['BaseConfig', 'NetConfig', 'Config', 'ConfigT']