Skip to content

Utilities

chanfig.utils

JsonEncoder

Bases: JSONEncoder

JSON encoder for Config.

Source code in chanfig/utils/io.py
Python
class JsonEncoder(JSONEncoder):
    r"""
    JSON encoder for Config.
    """

    def default(self, o: Any) -> Any:
        if hasattr(o, "__json__"):
            return o.__json__()
        if hasattr(o, "to_dict"):
            return o.to_dict()
        return super().default(o)

YamlDumper

Bases: SafeDumper

YAML Dumper for Config.

Source code in chanfig/utils/io.py
Python
class YamlDumper(SafeDumper):  # pylint: disable=R0903
    r"""
    YAML Dumper for Config.
    """

    def increase_indent(self, flow: bool = False, indentless: bool = False):  # pylint: disable=W0235
        return super().increase_indent(flow, indentless)

YamlLoader

Bases: SafeLoader

YAML Loader for Config.

Source code in chanfig/utils/io.py
Python
class YamlLoader(SafeLoader):
    r"""
    YAML Loader for Config.
    """

    def __init__(self, stream):
        super().__init__(stream)
        self._root = os.path.abspath(os.path.dirname(stream.name)) if hasattr(stream, "name") else os.getcwd()
        self.add_constructor("!include", self._include)
        self.add_constructor("!includes", self._includes)
        self.add_constructor("!env", self._env)

    @staticmethod
    def _include(loader: YamlLoader, node):
        relative_path = loader.construct_scalar(node)
        include_path = os.path.join(loader._root, relative_path)

        if not os.path.exists(include_path):
            raise FileNotFoundError(f"Included file not found: {include_path}")

        return load(include_path)

    @staticmethod
    def _includes(loader: YamlLoader, node):
        if not isinstance(node, SequenceNode):
            raise ConstructorError(None, None, f"!includes tag expects a sequence, got {node.id}", node.start_mark)
        files = loader.construct_sequence(node)
        return [YamlLoader._include(loader, ScalarNode("tag:yaml.org,2002:str", file)) for file in files]

    @staticmethod
    def _env(loader: YamlLoader, node):
        env_var = loader.construct_scalar(node)
        value = os.getenv(env_var)
        if value is None:
            raise ValueError(f"Environment variable '{env_var}' not set.")
        return value

NULL

NULL class.

get method in CHANfiG may accept None or Ellipse(...) as value of default. Therefore, it is mandatory to have a different default value for default.

Null is an instance of NULL and is recommended to be used as obj is Null.

Source code in chanfig/utils/null.py
Python
class NULL(metaclass=Singleton):
    r"""
    NULL class.

    `get` method in CHANfiG may accept `None` or `Ellipse`(`...`) as value of `default`.
    Therefore, it is mandatory to have a different default value for `default`.

    `Null` is an instance of `NULL` and is recommended to be used as `obj is Null`.
    """

    def __repr__(self):
        return "Null"

    def __nonzero__(self):
        return False

    def __len__(self):
        return 0

    def __call__(self, *args: Any, **kwargs: Any):
        return self

    def __contains__(self, name):
        return False

    def __iter__(self):
        return self

    def __next__(self):
        raise StopIteration

    def __getattr__(self, name):
        return self

    def __getitem__(self, index):
        return self

conform_annotation

Python
conform_annotation(data: Any, annotation: type) -> bool

Check if data is valid according to the expected type.

This function handles complex type annotations including: - Basic types (int, str, etc.) - Container types (List, Dict, etc.) - Union types (including Optional) - Nested generic types

Source code in chanfig/utils/annotation.py
Python
def conform_annotation(data: Any, annotation: type) -> bool:
    r"""
    Check if data is valid according to the expected type.

    This function handles complex type annotations including:
    - Basic types (int, str, etc.)
    - Container types (List, Dict, etc.)
    - Union types (including Optional)
    - Nested generic types
    """
    if annotation is Any:
        return True
    if annotation is type(None):
        return data is None
    origin_type = get_origin(annotation)
    arg_types = get_args(annotation)
    if origin_type in (Union, UnionType):
        return any(conform_annotation(data, arg_type) for arg_type in arg_types)
    if origin_type is Callable:
        return callable(data)
    if origin_type is not None and arg_types:
        if not isinstance(data, origin_type):
            return False
        if not data:
            return True
        if origin_type is tuple and len(arg_types) > 1 and arg_types[-1] is not Ellipsis:
            if len(data) != len(arg_types):
                return False
            return all(conform_annotation(item, type_) for item, type_ in zip(data, arg_types))
        if issubclass(origin_type, Sequence) and not isinstance(data, str):
            item_type = arg_types[0]
            return all(conform_annotation(item, item_type) for item in data)
        if issubclass(origin_type, Mapping):
            key_type, value_type = arg_types[:2]
            return all(conform_annotation(k, key_type) and conform_annotation(v, value_type) for k, v in data.items())
        if issubclass(origin_type, (set, frozenset)):
            item_type = arg_types[0]
            return all(conform_annotation(item, item_type) for item in data)
        return isinstance(data, origin_type)
    try:
        return isinstance(data, annotation)
    except TypeError:
        return False

get_annotations

Python
get_annotations(obj, *, globalns: Mapping | None = None, localns: Mapping | None = None, eval_str: bool = True) -> Mapping

Compute the annotations dict for an object.

obj may be a callable, class, or module. Passing in an object of any other type raises TypeError.

Returns a dict. get_annotations() returns a new dict every time it’s called; calling it twice on the same object will return two different but equivalent dicts.

This function handles several details for you:

  • If eval_str is true, values of type str will be un-stringized using eval(). This is intended for use with stringized annotations (from __future__ import annotations).
  • globalns fall back to public member of typing.
  • If obj doesn’t have an annotations dict, returns an empty dict. (Functions and methods always have an annotations dict; classes, modules, and other types of callables may not.)
  • Ignores inherited annotations on classes. If a class doesn’t have its own annotations dict, returns an empty dict.
  • All accesses to object members and dict values are done using getattr() and dict.get() for safety.
  • Always, always, always returns a freshly-created dict.

eval_str controls whether or not values of type str are replaced with the result of calling eval() on those values:

  • If eval_str is true, eval() is called on values of type str.
  • If eval_str is false (the default), values of type str are unchanged.

globalns and localns are passed in to eval(); see the documentation for eval() for more information.

globalns fall back to public member of typing.

If either globalns or localns is None, this function may replace that value with a context-specific default, contingent on type(obj):

  • If obj is a module, globalns defaults to obj.__dict__.
  • If obj is a class, globalns defaults to sys.modules[obj.__module__].__dict__ and localns defaults to the obj class namespace.
  • If obj is a callable, globalns defaults to obj.__globals__, although if obj is a wrapped function (using functools.update_wrapper()) it is first unwrapped.
  • If obj is an instance, globalns defaults to sys.modules[obj.__module__].__dict__ and localns defaults to the obj class namespace.
Source code in chanfig/utils/annotation.py
Python
@no_type_check
def get_annotations(  # pylint: disable=all
    obj, *, globalns: Mapping | None = None, localns: Mapping | None = None, eval_str: bool = True
) -> Mapping:
    r"""
    Compute the annotations dict for an object.

    obj may be a callable, class, or module.
    Passing in an object of any other type raises TypeError.

    Returns a dict.  get_annotations() returns a new dict every time
    it's called; calling it twice on the same object will return two
    different but equivalent dicts.

    This function handles several details for you:

      * If `eval_str` is true, values of type str will
        be un-stringized using `eval()`.  This is intended
        for use with stringized annotations
        (`from __future__ import annotations`).
      * `globalns` fall back to public member of `typing`.
      * If obj doesn't have an annotations dict, returns an
        empty dict.  (Functions and methods always have an
        annotations dict; classes, modules, and other types of
        callables may not.)
      * Ignores inherited annotations on classes.  If a class
        doesn't have its own annotations dict, returns an empty dict.
      * All accesses to object members and dict values are done
        using `getattr()` and `dict.get()` for safety.
      * Always, always, always returns a freshly-created dict.

    `eval_str` controls whether or not values of type str are replaced
    with the result of calling eval() on those values:

      * If `eval_str` is true, eval() is called on values of type str.
      * If `eval_str` is false (the default), values of type str are unchanged.

    `globalns` and `localns` are passed in to `eval()`; see the documentation
    for `eval()` for more information.

    `globalns` fall back to public member of `typing`.

    If either `globalns` or `localns` is
    None, this function may replace that value with a context-specific
    default, contingent on `type(obj)`:

      * If `obj` is a module, globalns defaults to `obj.__dict__`.
      * If `obj` is a class, globalns defaults to
        `sys.modules[obj.__module__].__dict__` and `localns`
        defaults to the obj class namespace.
      * If `obj` is a callable, `globalns` defaults to `obj.__globals__`,
        although if obj is a wrapped function (using
        functools.update_wrapper()) it is first unwrapped.
      * If `obj` is an instance, `globalns` defaults to
        `sys.modules[obj.__module__].__dict__` and localns
        defaults to the obj class namespace.
    """
    if isinstance(obj, type):
        # class
        annos = getattr(obj, "__annotations__", None)
        obj_globalns = None
        module_name = getattr(obj, "__module__", None)
        if module_name:
            module = sys.modules.get(module_name, None)
            if module:
                obj_globalns = getattr(module, "__dict__", None)
        obj_localns = dict(vars(obj))
        unwrap = obj
    elif isinstance(obj, ModuleType):
        # module
        annos = getattr(obj, "__annotations__", None)
        obj_globalns = getattr(obj, "__dict__")
        obj_localns = None
        unwrap = None
    elif callable(obj):
        # this includes types.Function, types.BuiltinFunctionType,
        # types.BuiltinMethodType, functools.partial, functools.singledispatch,
        # "class funclike" from Lib/test/test_inspect... on and on it goes.
        annos = getattr(obj, "__annotations__", None)
        obj_globalns = getattr(obj, "__globals__", None)
        obj_localns = None
        unwrap = obj
    else:
        # obj
        annos = getattr(type(obj), "__annotations__", None)
        obj_globalns = None
        module_name = getattr(obj, "__module__", None)
        if module_name:
            module = sys.modules.get(module_name, None)
            if module:
                obj_globalns = getattr(module, "__dict__", None)
        obj_localns = dict(vars(obj))
        unwrap = obj

    if annos is None or not annos:
        return {}

    if not isinstance(annos, dict):
        raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None")

    if unwrap is not None:
        while True:
            if hasattr(unwrap, "__wrapped__"):
                unwrap = unwrap.__wrapped__
                continue
            if isinstance(unwrap, partial):
                unwrap = unwrap.func
                continue
            break
        if hasattr(unwrap, "__globals__"):
            obj_globalns = unwrap.__globals__

    # globalns = GLOBAL_NS | globalns if globalns is not None else obj_globalns
    if globalns is None:
        globalns = obj_globalns
    globalns = {**GLOBAL_NS, **globalns}
    if localns is None:
        localns = obj_localns

    ret = {}
    for key, value in annos.items():
        if eval_str and isinstance(value, str):
            try:
                value = eval(value, globalns, localns)  # pylint: disable=W0123
            except NameError:
                raise ValueError(
                    f"Type annotation '{key}: {value}' in {obj!r} is invalid.\n"
                    "If you are running on an earlier version of Python, "
                    "please ensure annotations does not contain forward references."
                ) from None
            except TypeError:
                raise ValueError(
                    f"Type annotation '{key}: {value}' in {obj!r} is invalid.\n"
                    "If you are running on an earlier version of Python, "
                    "please ensure you are not using future features such as PEP604."
                ) from None
        ret[key] = value
    return ret

honor_annotation

Python
honor_annotation(data: Any, annotation: type) -> Any

Attempt to convert data to match the expected type annotation.

This function tries to honor the type annotation by converting the data when possible, rather than rejecting it. It works with: - Basic types (int, str, etc.) - Container types (List, Dict, etc.) - Union types (including Optional) - Nested generic types

Unlike conform_annotation which validates type compatibility, this function tries to adapt the data to match the annotation.

Examples:

Python Console Session
1
2
3
4
5
6
7
8
>>> honor_annotation("42", int)
42
>>> honor_annotation(42, str)
'42'
>>> honor_annotation("name", Union[int, str])
'name'
>>> honor_annotation(123, Union[int, str])
123
Source code in chanfig/utils/annotation.py
Python
def honor_annotation(data: Any, annotation: type) -> Any:
    r"""
    Attempt to convert data to match the expected type annotation.

    This function tries to honor the type annotation by converting the data
    when possible, rather than rejecting it. It works with:
    - Basic types (int, str, etc.)
    - Container types (List, Dict, etc.)
    - Union types (including Optional)
    - Nested generic types

    Unlike `conform_annotation` which validates type compatibility,
    this function tries to adapt the data to match the annotation.

    Examples:
        >>> honor_annotation("42", int)
        42
        >>> honor_annotation(42, str)
        '42'
        >>> honor_annotation("name", Union[int, str])
        'name'
        >>> honor_annotation(123, Union[int, str])
        123
    """
    if data is None or annotation is Any:
        return data
    origin_type = get_origin(annotation)
    arg_types = get_args(annotation)
    with suppress(Exception):
        if origin_type is Union or origin_type is UnionType:
            if any(conform_annotation(data, t) for t in arg_types):
                return data
            for t in arg_types:
                if t is not type(None):
                    with suppress(ValueError, TypeError):
                        return t(data)
            return data
        if origin_type is not None and arg_types:
            if issubclass(origin_type, tuple) and len(arg_types) == len(data):
                return origin_type(honor_annotation(item, arg) for item, arg in zip(data, arg_types))
            if isinstance(data, origin_type):
                if not data:
                    return data
                if origin_type is tuple and len(arg_types) > 1 and arg_types[-1] is not Ellipsis:
                    if len(data) == len(arg_types):
                        return tuple(honor_annotation(item, arg) for item, arg in zip(data, arg_types))
                    return data
                if issubclass(origin_type, Sequence) and not isinstance(data, str):
                    item_type = arg_types[0]
                    item_origin = get_origin(item_type)
                    item_args = get_args(item_type)
                    if item_origin is not None and item_args:
                        return origin_type([honor_annotation(item, item_type) for item in data])
                    return origin_type(honor_annotation(item, item_type) for item in data)
                if issubclass(origin_type, Mapping):
                    key_type, value_type = arg_types[:2]
                    return origin_type(
                        {honor_annotation(k, key_type): honor_annotation(v, value_type) for k, v in data.items()}
                    )
                if issubclass(origin_type, (set, frozenset)):
                    item_type = arg_types[0]
                    return origin_type(honor_annotation(item, item_type) for item in data)
            else:
                with suppress(ValueError, TypeError):
                    return origin_type(data)
            return data
        if isinstance(annotation, type) and not isinstance(data, annotation):
            with suppress(ValueError, TypeError):
                return annotation(data)
            return data
    return data

apply

Python
apply(obj: Any, func: Callable, *args: Any, **kwargs: Any) -> Any

Apply func to all children of obj.

Note that this method is meant for non-in-place modification of obj and should return the original object.

Parameters:

Name Type Description Default

obj

Any

Object to apply function.

required

func

Callable

Function to be applied.

required

*args

Any

Positional arguments to be passed to func.

()

**kwargs

Any

Keyword arguments to be passed to func.

{}

Returns:

Type Description
Any

Return value of func.

See Also

[apply_][chanfig.utils.apply.apply_]: Apply an in-place operation.

Source code in chanfig/utils/functional.py
Python
def apply(obj: Any, func: Callable, *args: Any, **kwargs: Any) -> Any:
    r"""
    Apply `func` to all children of `obj`.

    Note that this method is meant for non-in-place modification of `obj` and should return the original object.

    Args:
        obj: Object to apply function.
        func: Function to be applied.
        *args: Positional arguments to be passed to `func`.
        **kwargs: Keyword arguments to be passed to `func`.

    Returns:
        (Any): Return value of `func`.

    See Also:
        [`apply_`][chanfig.utils.apply.apply_]: Apply an in-place operation.
    """
    # Handle circular import
    from ..nested_dict import NestedDict

    if isinstance(obj, NestedDict):
        return obj.empty_like(**{k: apply(v, func, *args, **kwargs) for k, v in obj.items()})
    if isinstance(obj, Mapping):
        return {k: apply(v, func, *args, **kwargs) for k, v in obj.items()}
    if isinstance(obj, list):
        return [apply(v, func, *args, **kwargs) for v in obj]
    if isinstance(obj, tuple):
        return tuple(apply(v, func, *args, **kwargs) for v in obj)
    if isinstance(obj, set):
        try:
            return {apply(v, func, *args, **kwargs) for v in obj}
        except TypeError:
            return tuple(apply(v, func, *args, **kwargs) for v in obj)
    return func(*args, **kwargs) if ismethod(func) else func(obj, *args, **kwargs)

apply_

Python
apply_(obj: Any, func: Callable, *args: Any, **kwargs: Any) -> Any

Apply func to all children of obj.

Note that this method is meant for in-place modification of obj and should return the modified object.

Parameters:

Name Type Description Default

obj

Any

Object to apply function.

required

func

Callable

Function to be applied.

required

*args

Any

Positional arguments to be passed to func.

()

**kwargs

Any

Keyword arguments to be passed to func.

{}

Returns:

Type Description
Any

Return value of func.

See Also

[apply][chanfig.utils.apply.apply]: Apply a non-in-place operation.

Source code in chanfig/utils/functional.py
Python
def apply_(obj: Any, func: Callable, *args: Any, **kwargs: Any) -> Any:
    r"""
    Apply `func` to all children of `obj`.

    Note that this method is meant for in-place modification of `obj` and should return the modified object.

    Args:
        obj: Object to apply function.
        func: Function to be applied.
        *args: Positional arguments to be passed to `func`.
        **kwargs: Keyword arguments to be passed to `func`.

    Returns:
        (Any): Return value of `func`.

    See Also:
        [`apply`][chanfig.utils.apply.apply]: Apply a non-in-place operation.
    """
    # pylint: disable=C0103

    if isinstance(obj, Mapping):
        for v in obj.values():
            apply_(v, func, *args, **kwargs)
    if isinstance(obj, (list, tuple, set)):
        for v in obj:
            apply_(v, func, *args, **kwargs)
    return func(*args, **kwargs) if ismethod(func) else func(obj, *args, **kwargs)

parse_bool

Python
parse_bool(value: bool | str | int) -> bool

Convert various types of values to boolean.

This function converts different input types (bool, str, int) to their boolean equivalent.

Examples:

Python Console Session
>>> parse_bool(True)
True
>>> parse_bool("yes")
True
>>> parse_bool(1)
True
>>> parse_bool(0)
False
>>> parse_bool("false")
False
Source code in chanfig/utils/functional.py
Python
def parse_bool(value: bool | str | int) -> bool:
    r"""
    Convert various types of values to boolean.

    This function converts different input types (bool, str, int) to their boolean equivalent.

    Examples:
        >>> parse_bool(True)
        True
        >>> parse_bool("yes")
        True
        >>> parse_bool(1)
        True
        >>> parse_bool(0)
        False
        >>> parse_bool("false")
        False
    """
    if isinstance(value, bool):
        return value
    if isinstance(value, int):
        if value == 1:
            return True
        if value == 0:
            return False
        raise ValueError(f"Only 0 or 1 is allowed for boolean value, but got {value}.")
    if isinstance(value, str):
        if value.lower() in ("yes", "true", "t", "y", "1"):
            return True
        if value.lower() in ("no", "false", "f", "n", "0"):
            return False
    raise ValueError(f"Boolean value is expected, but got {value}.")

to_chanfig

Python
to_chanfig(obj: Any, cls: type | None = None) -> Any

Convert arbitrary data structure to CHANfiG objects when possible.

This function recursively converts mappings to FlatDict instances and handles nested structures of arbitrary depth.

Parameters:

Name Type Description Default

obj

Any

Object to be converted.

required

cls

type | None

Class to use for creating FlatDict instances. Defaults to FlatDict.

None

Returns:

Type Description
Any

Converted object.

Examples:

Python Console Session
>>> to_chanfig({'a': 1, 'b': 2})
FlatDict(
  ('a'): 1
  ('b'): 2
)
>>> to_chanfig([1, 2, 3])
[1, 2, 3]
>>> to_chanfig([{'a': 1}, {'b': 2}])
[FlatDict(('a'): 1), FlatDict(('b'): 2)]
>>> to_chanfig([[1, 2], [3, 4]])
FlatDict(
  (1): 2
  (3): 4
)
>>> to_chanfig([[1, 2, 3], [4, 5, 6]])
[[1, 2, 3], [4, 5, 6]]
Source code in chanfig/utils/functional.py
Python
def to_chanfig(obj: Any, cls: type | None = None) -> Any:
    r"""
    Convert arbitrary data structure to CHANfiG objects when possible.

    This function recursively converts mappings to FlatDict instances
    and handles nested structures of arbitrary depth.

    Args:
        obj: Object to be converted.
        cls: Class to use for creating FlatDict instances. Defaults to FlatDict.

    Returns:
        Converted object.

    Examples:
        >>> to_chanfig({'a': 1, 'b': 2})
        FlatDict(
          ('a'): 1
          ('b'): 2
        )
        >>> to_chanfig([1, 2, 3])
        [1, 2, 3]
        >>> to_chanfig([{'a': 1}, {'b': 2}])
        [FlatDict(('a'): 1), FlatDict(('b'): 2)]
        >>> to_chanfig([[1, 2], [3, 4]])
        FlatDict(
          (1): 2
          (3): 4
        )
        >>> to_chanfig([[1, 2, 3], [4, 5, 6]])
        [[1, 2, 3], [4, 5, 6]]
    """
    # Handle circular import
    from ..flat_dict import FlatDict

    if cls is None:
        cls = FlatDict

    if isinstance(obj, Mapping):
        result = cls()
        for k, v in obj.items():
            result[k] = to_chanfig(v, cls)
        return result
    if isinstance(obj, (list, tuple)) and all(isinstance(item, (list, tuple)) and len(item) == 2 for item in obj):
        try:
            result = cls()
            for k, v in obj:
                result[k] = to_chanfig(v, cls)
            return result
        except (ValueError, TypeError):
            pass
    if isinstance(obj, (list, tuple)):
        return type(obj)(to_chanfig(item, cls) for item in obj)
    if isinstance(obj, set):
        try:
            return {to_chanfig(item, cls) for item in obj}
        except TypeError:
            return tuple(to_chanfig(item, cls) for item in obj)
    return obj

to_dict

Python
to_dict(obj: Any, flatten: bool = False) -> Mapping | Sequence | Set

Convert an object to a dict.

Note that when converting a set object, it may be converted to a tuple object if its values is not hashable.

Parameters:

Name Type Description Default

obj

Any

Object to be converted.

required

flatten

bool

Whether to flatten nested structures.

False

Returns:

Type Description
Mapping | Sequence | Set

A dict.

Examples:

Python Console Session
>>> from chanfig import FlatDict, Variable
Python Console Session
>>> to_dict(1)
1
>>> to_dict([1, 2, 3])
[1, 2, 3]
>>> to_dict((1, 2, 3))
(1, 2, 3)
>>> to_dict({1, 2, 3})
{1, 2, 3}
>>> to_dict({'a': 1, 'b': 2})
{'a': 1, 'b': 2}
>>> to_dict(Variable(1))
1
>>> to_dict(FlatDict(a=[[[[[FlatDict(b=1)]]]]]))
{'a': [[[[[{'b': 1}]]]]]}
>>> to_dict(FlatDict(a={FlatDict(b=1)}))
{'a': ({'b': 1},)}
Source code in chanfig/utils/functional.py
Python
def to_dict(obj: Any, flatten: bool = False) -> Mapping | Sequence | Set:
    r"""
    Convert an object to a dict.

    Note that when converting a `set` object, it may be converted to a `tuple` object if its values is not hashable.

    Args:
        obj: Object to be converted.
        flatten: Whether to flatten nested structures.

    Returns:
        A dict.

    Examples:
        >>> from chanfig import FlatDict, Variable

        >>> to_dict(1)
        1
        >>> to_dict([1, 2, 3])
        [1, 2, 3]
        >>> to_dict((1, 2, 3))
        (1, 2, 3)
        >>> to_dict({1, 2, 3})
        {1, 2, 3}
        >>> to_dict({'a': 1, 'b': 2})
        {'a': 1, 'b': 2}
        >>> to_dict(Variable(1))
        1
        >>> to_dict(FlatDict(a=[[[[[FlatDict(b=1)]]]]]))
        {'a': [[[[[{'b': 1}]]]]]}
        >>> to_dict(FlatDict(a={FlatDict(b=1)}))
        {'a': ({'b': 1},)}
    """
    # Handle circular import
    from ..flat_dict import FlatDict
    from ..variable import Variable

    if flatten and isinstance(obj, FlatDict):
        return {k: to_dict(v) for k, v in obj.all_items()}
    if isinstance(obj, Mapping):
        return {k: to_dict(v) for k, v in obj.items()}
    if isinstance(obj, list):
        return [to_dict(v) for v in obj]
    if isinstance(obj, tuple):
        return tuple(to_dict(v) for v in obj)
    if isinstance(obj, set):
        try:
            return {to_dict(v) for v in obj}
        except TypeError:
            return tuple(to_dict(v) for v in obj)
    if isinstance(obj, Variable):
        return obj.value
    if is_dataclass(obj):
        return asdict(obj)  # type: ignore[arg-type]
    if hasattr(obj, "to_dict"):
        return obj.to_dict()
    return obj

load

Python
load(file: PathStr, cls=None, *args: Any, **kwargs: Any) -> Any

Load a file into a FlatDict.

This function simply calls cls.load, by default, cls is NestedDict.

Parameters:

Name Type Description Default

file

PathStr

The file to load.

required

cls

The class of the file to load. Defaults to NestedDict.

None

*args

Any

The arguments to pass to NestedDict.load.

()

**kwargs

Any

The keyword arguments to pass to NestedDict.load.

{}
See Also

load

Examples:

Python Console Session
1
2
3
4
5
6
7
8
>>> from chanfig import load
>>> config = load("tests/test.yaml")
>>> config
NestedDict(
  ('a'): 1
  ('b'): 2
  ('c'): 3
)
Source code in chanfig/utils/io.py
Python
def load(file: PathStr, cls=None, *args: Any, **kwargs: Any) -> Any:  # pylint: disable=W1113
    r"""
    Load a file into a `FlatDict`.

    This function simply calls `cls.load`, by default, `cls` is `NestedDict`.

    Args:
        file: The file to load.
        cls: The class of the file to load. Defaults to `NestedDict`.
        *args: The arguments to pass to `NestedDict.load`.
        **kwargs: The keyword arguments to pass to `NestedDict.load`.

    See Also:
        [`load`][chanfig.FlatDict.load]

    Examples:
        >>> from chanfig import load
        >>> config = load("tests/test.yaml")
        >>> config
        NestedDict(
          ('a'): 1
          ('b'): 2
          ('c'): 3
        )
    """
    # Import here to avoid circular imports
    from ..nested_dict import NestedDict

    if cls is None:
        cls = NestedDict

    return cls.load(file, *args, **kwargs)

save

Python
save(obj, file: File, method: str = None, *args: Any, **kwargs: Any) -> None

Save FlatDict to file.

Raises:

Type Description
ValueError

If save to IO and method is not specified.

TypeError

If save to unsupported extension.

Alias:

  • save

Examples:

Python Console Session
>>> obj = {"a": 1, "b": 2, "c": 3}
>>> save(obj, "test.yaml")
>>> save(obj, "test.json")
>>> save(obj, "test.conf")
Traceback (most recent call last):
TypeError: `file='test.conf'` should be in ('json',) or ('yml', 'yaml'), but got conf.
>>> with open("test.yaml", "w") as f:
...     save(obj, f)
Traceback (most recent call last):
ValueError: `method` must be specified when saving to IO.
Source code in chanfig/utils/io.py
Python
def save(  # pylint: disable=W1113
    obj, file: File, method: str = None, *args: Any, **kwargs: Any  # type: ignore[assignment]
) -> None:
    r"""
    Save `FlatDict` to file.

    Raises:
        ValueError: If save to `IO` and `method` is not specified.
        TypeError: If save to unsupported extension.

    **Alias**:

    + `save`

    Examples:
        >>> obj = {"a": 1, "b": 2, "c": 3}
        >>> save(obj, "test.yaml")
        >>> save(obj, "test.json")
        >>> save(obj, "test.conf")
        Traceback (most recent call last):
        TypeError: `file='test.conf'` should be in ('json',) or ('yml', 'yaml'), but got conf.
        >>> with open("test.yaml", "w") as f:
        ...     save(obj, f)
        Traceback (most recent call last):
        ValueError: `method` must be specified when saving to IO.
    """
    # Import FlatDict here to avoid circular imports
    from ..flat_dict import FlatDict, to_dict

    if isinstance(obj, FlatDict):
        obj.save(file, method, *args, **kwargs)
        return

    data = to_dict(obj)
    if method is None:
        if isinstance(file, IOBase):
            raise ValueError("`method` must be specified when saving to IO.")
        method = splitext(file)[-1][1:]
    extension = method.lower()
    if extension in YAML_EXTENSIONS:
        with FlatDict.open(file, mode="w") as fp:  # pylint: disable=C0103
            yaml_dump(data, fp, *args, **kwargs)
        return
    if extension in JSON_EXTENSIONS:
        with FlatDict.open(file, mode="w") as fp:  # pylint: disable=C0103
            fp.write(json_dumps(data, *args, **kwargs))
        return
    raise TypeError(f"`file={file!r}` should be in {JSON_EXTENSIONS} or {YAML_EXTENSIONS}, but got {extension}.")

find_circular_reference

Python
find_circular_reference(graph: Mapping) -> list[str] | None

Find circular references in a dependency graph.

This function performs a depth-first search to detect any circular references in a graph represented as a mapping of nodes to their dependencies.

Source code in chanfig/utils/placeholder.py
Python
def find_circular_reference(graph: Mapping) -> list[str] | None:
    r"""
    Find circular references in a dependency graph.

    This function performs a depth-first search to detect any circular references
    in a graph represented as a mapping of nodes to their dependencies.
    """

    def dfs(node, visited, path):  # pylint: disable=R1710
        path.append(node)
        if node in visited:
            return path
        visited.add(node)
        for child in graph.get(node, []):
            result = dfs(child, visited, path)
            if result is not None:
                return result
        visited.remove(node)

    for key in graph:
        result = dfs(key, set(), [])
        if result is not None:
            return result

    return None

find_placeholders

Python
find_placeholders(text: str) -> list[str]

Find all placeholders in text, including nested ones.

This function searches for placeholders in the format ${name} and returns a list of all placeholder names found, including those that are nested within other placeholders.

Examples:

Python Console Session
1
2
3
4
5
6
>>> find_placeholders("Hello ${name}")
['name']
>>> find_placeholders("Hello ${user.${type}}")
['user.${type}', 'type']
>>> find_placeholders("${outer${inner}}")
['outer${inner}', 'inner']
Source code in chanfig/utils/placeholder.py
Python
def find_placeholders(text: str) -> list[str]:
    r"""Find all placeholders in text, including nested ones.

    This function searches for placeholders in the format ${name} and returns a list
    of all placeholder names found, including those that are nested within other placeholders.

    Examples:
        >>> find_placeholders("Hello ${name}")
        ['name']
        >>> find_placeholders("Hello ${user.${type}}")
        ['user.${type}', 'type']
        >>> find_placeholders("${outer${inner}}")
        ['outer${inner}', 'inner']
    """
    if not isinstance(text, str):
        return []

    results = []
    stack = []
    i = 0

    while i < len(text):
        if text[i : i + 2] == "${":  # noqa: E203
            stack.append(i)
            i += 2
        elif text[i] == "}" and stack:
            start = stack.pop()
            placeholder = text[start + 2 : i]  # noqa: E203
            if not stack:
                results.append(placeholder)
            i += 1
        else:
            i += 1

    nested_results = []
    for placeholder in results:
        nested_results.extend(find_placeholders(placeholder))

    return results + nested_results