Source code for rwskit.collections_

"""Utilities for working with collections."""

# Python Modules
from __future__ import annotations

import logging as log

from types import GeneratorType
from typing import Any, Iterable, Mapping, Optional

# 3rd Party Modules

# Project Modules


[docs] log = log.getLogger(__name__)
[docs] def is_iterable(obj: Any, consider_string_iterable: bool = False) -> bool: """ Tests if the object is iterable. Parameters ---------- obj : any An object. consider_string_iterable : bool, default = False Whether to consider strings iterable or not. Returns ------- bool ``True`` if ``obj`` is iterable. """ if isinstance(obj, str): return consider_string_iterable try: iter(obj) except TypeError: return False else: return True
[docs] def is_generator(obj: Any) -> bool: """ Checks if the object is a generator. Parameters ---------- obj : any An object. Returns ------- bool ``True`` if the object is a generator. """ return isinstance(obj, GeneratorType)
[docs] def get_first_non_null_value(collection: Iterable) -> Optional[Any]: """Recursively try to get the first non-null value in a collection. This method will recursively traverse the collection until it finds a non-iterable value that is not ``None``. Parameters ---------- collection : Iterable The collection to retrieve the value from. Returns ------- Any, optional The first non-null value in the series, if one exists, otherwise return ``None``. """ for value in collection: if is_iterable(value) and not isinstance(value, str): value = get_first_non_null_value(value) if value is not None: return value return None
[docs] def recursive_sort(obj: Any) -> Any: """ Attempts to sort an object recursively according to the following rules: * If the object is a dictionary, it will be sorted by its keys and its values will be sorted recursively. * If the object is a list or tuple, it will be sorted by its values. Typically, a value is a primitive, but lists, tuples, and dictionaries are also valid. In these cases, the collections are compared after sorting. * Any other value is returned unchanged. Parameters ---------- obj : Any The input object Returns ------- Any The returned object sorted. """ def _key_fn(e: Any) -> Any: if isinstance(e, dict): return sorted(e.items()) elif isinstance(e, (list, tuple)): return sorted(e) else: return e if isinstance(obj, dict): return {k: recursive_sort(obj[k]) for k in sorted(obj)} elif isinstance(obj, list): values = sorted(obj, key=_key_fn) return [recursive_sort(e) for e in values] elif isinstance(obj, tuple): values = sorted(obj, key=_key_fn) return tuple([recursive_sort(e) for e in values]) elif is_iterable(obj) or is_generator(obj): return list(obj) else: return obj
[docs] def remove_none_from_dict(d: dict[str, Any]) -> dict[str, Any]: """Recursively remove ``None`` values from a (nested) dictionary.""" def _do_removal(obj: Any) -> Any: if isinstance(obj, Mapping): return {k: _do_removal(v) for k, v in obj.items() if v is not None} elif isinstance(obj, list): return [remove_none_from_dict(e) for e in obj] elif isinstance(obj, set): return {remove_none_from_dict(e) for e in obj} else: return obj return _do_removal(d)