File size: 2,400 Bytes
9c6594c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
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
59
60
61
62
63
64
65
66
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Iterable, TypeVar, Union, overload

if TYPE_CHECKING:
    T = TypeVar("T")
    ClassInfo = Union[type[T], tuple[type[T], ...]]


@overload
def always_list(obj: Iterable[T], base_type: ClassInfo = ...) -> list[T]: ...
@overload
def always_list(obj: T, base_type: ClassInfo = ...) -> list[T]: ...
def always_list(obj: Any, base_type: Any = (str, bytes)) -> list[T]:
    """Return a guaranteed list of objects from a single instance OR iterable of such objects.

    By default, assume the returned list should have string-like elements (i.e. `str`/`bytes`).

    Adapted from `more_itertools.always_iterable`, but simplified for internal use.  See:
    https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.always_iterable
    """
    return [obj] if isinstance(obj, base_type) else list(obj)


def one(
    iterable: Iterable[T],
    too_short: type[Exception] | Exception | None = None,
    too_long: type[Exception] | Exception | None = None,
) -> T:
    """Return the only item in the iterable.

    Note:
        This is intended **only** as an internal helper/convenience function,
        and its implementation is directly adapted from `more_itertools.one`.
        Users needing similar functionality are strongly encouraged to use
        that library instead:
        https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.one

    Args:
        iterable: The iterable to get the only item from.
        too_short: Custom exception to raise if the iterable has no items.
        too_long: Custom exception to raise if the iterable has multiple items.

    Raises:
        ValueError or `too_short`: If the iterable has no items.
        ValueError or `too_long`: If the iterable has multiple items.
    """
    # For a general iterable, avoid inadvertently iterating through all values,
    # which may be costly or impossible (e.g. if infinite).  Only check that:

    # ... the first item exists
    it = iter(iterable)
    try:
        obj = next(it)
    except StopIteration:
        raise (too_short or ValueError("Expected 1 item in iterable, got 0")) from None

    # ...the second item doesn't
    try:
        _ = next(it)
    except StopIteration:
        return obj
    raise (
        too_long or ValueError("Expected 1 item in iterable, got multiple")
    ) from None