"""Base of config utilities implementation."""
from typing import Any, Optional, TypeVar
from dataclasses import dataclass, field
from omegaconf.basecontainer import BaseContainer
from omegaconf.base import SCMode
from omegaconf import OmegaConf
[docs]
class InstanceKeys:
"""
Collection of standard instance keys used for :class:`~coma.config.base.Config`
in core ``coma``. User-defined keys beyond these can exist.
Attributes:
BASE: The base instance that results from direct initialization.
FILE: The instance that results from loading a config from file.
OVERRIDE: The instance that results from overriding another instance
with command-line arguments.
"""
BASE: "InstanceKey" = "BASE"
FILE: "InstanceKey" = "FILE"
OVERRIDE: "InstanceKey" = "OVERRIDE"
T = TypeVar("T")
InstanceKey = str
"""Type alias for instance keys."""
Identifier = str
"""Type alias for a valid Python identifier."""
ConfigID = Identifier
"""Type alias for config identifiers. Must be a valid Python identifier."""
Configs = dict[ConfigID, "Config"]
"""
Type alias for mapping between config identifiers and
:class:`~coma.config.base.Config` s.
"""
ParamID = Identifier
"""Type alias for parameter identifiers. Must be a valid Python identifier."""
Parameters = dict[ParamID, Any]
"""Type alias for a mapping between parameter identifiers and values."""
[docs]
@dataclass
class Config:
"""
Wrapper for config data. Manages all viable instances of a config. Typically, a new
instance *variant* is created whenever major changes to the config data are made.
Attributes:
back_end (typing.Any): The backing construct of the config. Must be an
``omegaconf``-supported type, which means either a ``list`` object, a
``dict`` object, or a ``dataclass`` type or object.
instances (dict[:data:`~coma.config.base.InstanceKey`, typing.Any]): The
collection of instance variants of :obj:`back_end` mapped by variant keys.
"""
back_end: Any
instances: dict[InstanceKey, Any] = field(default_factory=dict)
[docs]
def has(self, key: InstanceKey) -> bool:
"""Returns whether this Config has an instance corresponding to :obj:`key`."""
return key in self.instances
[docs]
def get(self, key: InstanceKey) -> Any:
"""
Returns the instance corresponding to :obj:`key`.
Args:
key (:data:`~coma.config.base.InstanceKey`): The instance key.
Returns:
typing.Any: The corresponding instance.
Raises:
KeyError: If no corresponding instance exists.
"""
return self.instances[key]
[docs]
def set(self, key: InstanceKey, instance: Any, force_latest: bool = False) -> None:
"""
Sets or updates the instance corresponding to :obj:`key`.
Args:
key (:data:`~coma.config.base.InstanceKey`): The instance variant key
for which to set or update the instance data.
instance (typing.Any): The instance data to set for :obj:`key`.
force_latest (bool): Whether to force :obj:`key` to be interpreted
as the latest (at least until the next call to :obj:`set()`).
See also:
* :meth:`~coma.config.base.Config.get_latest()`
* :meth:`~coma.config.base.Config.get_latest_key()`
"""
if force_latest:
self.delete(key, raise_on_missing=False)
self.instances[key] = instance
[docs]
def delete(self, key: InstanceKey, raise_on_missing: bool = False) -> None:
"""
Deletes the instance corresponding to :obj:`key`.
Args:
key (:data:`~coma.config.base.InstanceKey`): The instance variant key
for which to delete the instance data.
raise_on_missing (bool): Whether to raise an error when no instance
variant corresponding to :obj:`key` exists or no nothing silently.
Raises:
KeyError: If no instance corresponding to :obj:`key` exists and
:obj:`raise_on_missing` is :obj:`True`.
"""
if raise_on_missing or self.has(key):
del self.instances[key]
[docs]
def get_latest(self) -> Any:
"""
Returns the latest instance.
.. note::
This is the latest by *insertion* order. Newer overwrites of existing
instances don't count as latest unless :obj:`force_latest` is :obj:`True`
on calls to :meth:`~coma.config.base.Config.set()`.
Raises:
ValueError: If there are no instances at all and so no latest instance.
See also:
* :meth:`~coma.config.base.Config.get_latest_key()`
"""
return self.instances[self.get_latest_key()]
[docs]
def get_latest_key(self) -> InstanceKey:
"""
Returns the key of the latest instance.
.. note::
This is the latest by *insertion* order. Newer overwrites of existing
instances don't count as latest unless :obj:`force_latest` is :obj:`True`
on calls to :meth:`~coma.config.base.Config.set()`.
Returns:
:data:`~coma.config.base.InstanceKey`: The key corresponding to the
latest instance to be set.
Raises:
ValueError: If there are no instances at all and so no latest instance.
See also:
* :meth:`~coma.config.base.Config.get_latest()`
"""
try:
return list(self.instances.keys())[-1]
except IndexError:
raise ValueError(f"No instances exist from which to retrieve the latest.")
[docs]
def get_or_latest(self, key: Optional[InstanceKey] = None) -> Any:
"""
Returns the instance corresponding to :obj:`key` *unless* :obj:`key` is
:obj:`None` in which case the latest instance is returned instead.
Returns:
typing.Any: The corresponding instance.
Raises:
KeyError: If :obj:`key` is not :obj:`None`, but no corresponding
instance exists.
ValueError: If :obj:`key` is :obj:`None`, but there are no instances
at all and so no latest instance.
See also:
* :meth:`~coma.config.base.Config.get_latest()`
"""
return self.get_latest() if key is None else self.get(key)
[docs]
def make_latest(self, key: InstanceKey) -> None:
"""
Forces the given :obj:`key` to be interpreted as the latest (at least
until a new key gets added).
Raises:
KeyError: If no corresponding instance exists.
See also:
* :meth:`~coma.config.base.Config.get_latest()`
"""
self.set(key, self.get(key), force_latest=True)
[docs]
def is_primitive(self, key: InstanceKey) -> bool:
"""
Returns whether the instance data corresponding to :obj:`key` is a
primitive Python object (``list``, ``dict``, or ``dataclass``) as
opposed to an ``omegaconf`` container object.
Raises:
KeyError: If no corresponding instance exists.
"""
return not OmegaConf.is_config(self.get(key))
[docs]
def as_primitive(
self,
key: InstanceKey,
*,
resolve: bool = True,
throw_on_missing: bool = True,
enum_to_str: bool = False,
structured_config_mode: SCMode = SCMode.INSTANTIATE,
) -> Any:
"""
Returns the instance data corresponding to :obj:`key` as a primitive
Python object (``list``, ``dict``, or ``dataclass``) instead of an
``omegaconf`` container object.
If the instance is already primitive, returns it directly.
Does not update the underlying instance. To do so, use
:meth:`~coma.config.base.Config.set()`.
Args:
key (:data:`~coma.config.base.InstanceKey`): The instance variant
key for which to convert the instance data into a primitive.
resolve (bool): Passed to `OmegaConf.to_container()`_.
throw_on_missing (bool): Passed to `OmegaConf.to_container()`_.
enum_to_str (bool): Passed to `OmegaConf.to_container()`_.
structured_config_mode (:class:`omegaconf.base.SCMode`): Passed to
`OmegaConf.to_container()`_.
Returns:
typing.Any: The instance data for :obj:`key` as a Python primitive.
Raises:
KeyError: If no corresponding instance exists.
Others: As may be raised by `OmegaConf.to_container()`_.
.. _OmegaConf.to_container():
https://omegaconf.readthedocs.io/en/2.1_branch/usage.html#omegaconf-to-container
"""
if self.is_primitive(key):
return self.get(key)
return OmegaConf.to_container(
self.get(key),
resolve=resolve,
throw_on_missing=throw_on_missing,
enum_to_str=enum_to_str,
structured_config_mode=structured_config_mode,
)
[docs]
def from_primitive(
self,
key: InstanceKey,
*,
parent: Optional[BaseContainer] = None,
flags: Optional[dict[str, bool]] = None,
) -> Any:
"""
Returns the instance data corresponding to :obj:`key` as an ``omegaconf``
container object instead of a primitive Python object (``list``, ``dict``,
or ``dataclass``).
If the instance is already an ``omegaconf`` container, returns it directly.
Does not update the underlying instance. To do so, use
:meth:`~coma.config.base.Config.set()`.
Args:
key (:data:`~coma.config.base.InstanceKey`): The instance variant
key for which to convert the instance data into a primitive.
parent (:class:`omegaconf.basecontainer.BaseContainer`, optional):
Passed to `OmegaConf.create()`_.
flags (dict[str, bool], optional): Passed to `OmegaConf.create()`_.
Returns:
typing.Any: The instance data for :obj:`key` as an ``omegaconf`` container.
Raises:
KeyError: If no corresponding instance exists.
Others: As may be raised by `OmegaConf.create()`_.
.. _OmegaConf.create():
https://omegaconf.readthedocs.io/en/2.1_branch/usage.html#creating
"""
if self.is_primitive(key):
return OmegaConf.create(self.get(key), parent, flags)
return self.get(key)