Skip to content

liblaf.grapes.itertools ยค

Functions:

as_iterable ยค

as_iterable(
    obj: Any, base_type: ClassInfo | None = (str, bytes)
) -> Iterable

.

Examples:

If obj is iterable, return an iterator over its items:

>>> obj = (1, 2, 3)
>>> as_iterable(obj)
(1, 2, 3)

If obj is not iterable, return a one-item iterable containing obj:

>>> obj = 1
>>> as_iterable(obj)
(1,)

If obj is None, return an empty iterable:

>>> obj = None
>>> as_iterable(None)
()

By default, binary and text strings are not considered iterable:

>>> obj = "foo"
>>> as_iterable(obj)
('foo',)

If base_type is set, objects for which isinstance(obj, base_type) returns True won't be considered iterable.

>>> obj = {"a": 1}
>>> as_iterable(obj)
{'a': 1}
>>> as_iterable(obj, base_type=dict)  # Treat dicts as a unit
({'a': 1},)

Set base_type to None to avoid any special handling and treat objects Python considers iterable as iterable:

>>> obj = "foo"
>>> as_iterable(obj, base_type=None)
'foo'
References
  1. more_itertools.always_iterable
Source code in src/liblaf/grapes/itertools/_as_iterable.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def as_iterable(obj: Any, base_type: ClassInfo | None = (str, bytes)) -> Iterable:
    """.

    Examples:
        If `obj` is iterable, return an iterator over its items:

        >>> obj = (1, 2, 3)
        >>> as_iterable(obj)
        (1, 2, 3)

        If `obj` is not iterable, return a one-item iterable containing `obj`:

        >>> obj = 1
        >>> as_iterable(obj)
        (1,)

        If `obj` is `None`, return an empty iterable:

        >>> obj = None
        >>> as_iterable(None)
        ()

        By default, binary and text strings are not considered iterable:

        >>> obj = "foo"
        >>> as_iterable(obj)
        ('foo',)

        If `base_type` is set, objects for which `isinstance(obj, base_type)` returns ``True`` won't be considered iterable.

        >>> obj = {"a": 1}
        >>> as_iterable(obj)
        {'a': 1}
        >>> as_iterable(obj, base_type=dict)  # Treat dicts as a unit
        ({'a': 1},)

        Set `base_type` to `None` to avoid any special handling and treat objects Python considers iterable as iterable:

        >>> obj = "foo"
        >>> as_iterable(obj, base_type=None)
        'foo'

    References:
        1. [`more_itertools.always_iterable`](https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.always_iterable)
    """
    if obj is None:
        return ()
    if base_type is not None and isinstance(obj, base_type):
        return (obj,)
    if isinstance(obj, Iterable):
        return obj
    return (obj,)

as_sequence ยค

as_sequence(
    obj: Any, base_type: ClassInfo | None = (str, bytes)
) -> Sequence

.

Examples:

If obj is iterable, return an iterator over its items:

>>> obj = (1, 2, 3)
>>> as_sequence(obj)
(1, 2, 3)

If obj is not iterable, return a one-item iterable containing obj:

>>> obj = 1
>>> as_sequence(obj)
(1,)

If obj is None, return an empty iterable:

>>> obj = None
>>> as_sequence(None)
()

By default, binary and text strings are not considered iterable:

>>> obj = "foo"
>>> as_sequence(obj)
('foo',)

If base_type is set, objects for which isinstance(obj, base_type) returns True won't be considered iterable.

>>> obj = {"a": 1}
>>> as_sequence(obj)
({'a': 1},)
>>> as_sequence(obj, base_type=dict)  # Treat dicts as a unit
({'a': 1},)

Set base_type to None to avoid any special handling and treat objects Python considers iterable as iterable:

>>> obj = "foo"
>>> as_sequence(obj, base_type=None)
'foo'
References
  1. more_itertools.always_iterable
Source code in src/liblaf/grapes/itertools/_as_sequence.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def as_sequence(obj: Any, base_type: ClassInfo | None = (str, bytes)) -> Sequence:
    """.

    Examples:
        If `obj` is iterable, return an iterator over its items:

        >>> obj = (1, 2, 3)
        >>> as_sequence(obj)
        (1, 2, 3)

        If `obj` is not iterable, return a one-item iterable containing `obj`:

        >>> obj = 1
        >>> as_sequence(obj)
        (1,)

        If `obj` is `None`, return an empty iterable:

        >>> obj = None
        >>> as_sequence(None)
        ()

        By default, binary and text strings are not considered iterable:

        >>> obj = "foo"
        >>> as_sequence(obj)
        ('foo',)

        If `base_type` is set, objects for which `isinstance(obj, base_type)` returns ``True`` won't be considered iterable.

        >>> obj = {"a": 1}
        >>> as_sequence(obj)
        ({'a': 1},)
        >>> as_sequence(obj, base_type=dict)  # Treat dicts as a unit
        ({'a': 1},)

        Set `base_type` to `None` to avoid any special handling and treat objects Python considers iterable as iterable:

        >>> obj = "foo"
        >>> as_sequence(obj, base_type=None)
        'foo'

    References:
        1. [`more_itertools.always_iterable`](https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.always_iterable)
    """
    if obj is None:
        return ()
    if base_type is not None and isinstance(obj, base_type):
        return (obj,)
    if isinstance(obj, Sequence):
        return obj
    return (obj,)

deep_merge ยค

deep_merge(
    *mappings: Mapping[KT, VT], append_arrays: bool = False
) -> dict[KT, VT]
Source code in src/liblaf/grapes/itertools/_deep_merge.py
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
def deep_merge[KT, VT](
    *mappings: Mapping[KT, VT], append_arrays: bool = False
) -> dict[KT, VT]:
    result: dict[KT, VT] = {}
    for mapping in mappings:
        for key, value in mapping.items():
            if key not in result:
                result[key] = value
            elif isinstance(result[key], Mapping):
                result[key] = deep_merge(  # pyright: ignore[reportArgumentType]
                    result[key],  # pyright: ignore[reportArgumentType]
                    value,  # pyright: ignore[reportArgumentType]
                    append_arrays=append_arrays,
                )
            elif append_arrays and isinstance(result[key], Sequence):
                result[key] = [*result[key], *value]  # pyright: ignore[reportArgumentType, reportGeneralTypeIssues]
    return result

first_not_none ยค

first_not_none(*args: T | None) -> T

Returns the first not None value in the args.

Examples:

>>> first_not_none(1, 2)
1
>>> first_not_none(None, 1)
1
References
  1. more_itertools.first_true
Source code in src/liblaf/grapes/itertools/_first_not_none.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def first_not_none[T](*args: T | None) -> T:
    """Returns the first `not None` value in the `args`.

    Examples:
        >>> first_not_none(1, 2)
        1
        >>> first_not_none(None, 1)
        1

    References:
        1. [`more_itertools.first_true`](https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.first_true)
    """
    return next(arg for arg in args if arg is not None)

len_or_none ยค

len_or_none(iterable: Iterable) -> int | None
Source code in src/liblaf/grapes/itertools/_len_or_none.py
4
5
6
7
8
def len_or_none(iterable: Iterable) -> int | None:
    try:
        return len(iterable)  # pyright: ignore[reportArgumentType]
    except TypeError:
        return None