python-utils 2.5.6__py3-none-any.whl → 4.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_utils/__about__.py +35 -7
- python_utils/__init__.py +241 -0
- python_utils/_aliases.py +53 -0
- python_utils/aio.py +133 -0
- python_utils/containers.py +637 -0
- python_utils/converters.py +265 -85
- python_utils/decorators.py +216 -6
- python_utils/exceptions.py +47 -0
- python_utils/formatters.py +72 -16
- python_utils/generators.py +126 -0
- python_utils/import_.py +64 -26
- python_utils/logger.py +352 -29
- python_utils/loguru.py +53 -0
- python_utils/terminal.py +127 -67
- python_utils/time.py +371 -18
- python_utils/types.py +179 -0
- python_utils-4.0.0.dist-info/METADATA +389 -0
- python_utils-4.0.0.dist-info/RECORD +21 -0
- {python_utils-2.5.6.dist-info → python_utils-4.0.0.dist-info}/WHEEL +1 -3
- python_utils-2.5.6.dist-info/METADATA +0 -122
- python_utils-2.5.6.dist-info/RECORD +0 -15
- python_utils-2.5.6.dist-info/top_level.txt +0 -1
- /python_utils/{compat.py → py.typed} +0 -0
- {python_utils-2.5.6.dist-info → python_utils-4.0.0.dist-info/licenses}/LICENSE +0 -0
python_utils/__about__.py
CHANGED
|
@@ -1,9 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
"""
|
|
2
|
+
This module contains metadata about the `python-utils` package.
|
|
3
|
+
|
|
4
|
+
Attributes:
|
|
5
|
+
__package_name__ (str): The name of the package.
|
|
6
|
+
__author__ (str): The author of the package.
|
|
7
|
+
__author_email__ (str): The email of the author.
|
|
8
|
+
__description__ (str): A brief description of the package.
|
|
9
|
+
__url__ (str): The URL of the package's repository.
|
|
10
|
+
__version__ (str): The current version, read from the installed metadata.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from importlib import metadata
|
|
16
|
+
|
|
17
|
+
#: Distribution name as published on PyPI.
|
|
18
|
+
__package_name__: str = 'python-utils'
|
|
19
|
+
#: Primary author's name.
|
|
20
|
+
__author__: str = 'Rick van Hattem'
|
|
21
|
+
#: Primary author's contact email.
|
|
22
|
+
__author_email__: str = 'Wolph@wol.ph'
|
|
23
|
+
#: One-line description of the package.
|
|
24
|
+
__description__: str = (
|
|
6
25
|
'Python Utils is a module with some convenient utilities not included '
|
|
7
|
-
'with the standard Python install'
|
|
8
|
-
|
|
26
|
+
'with the standard Python install'
|
|
27
|
+
)
|
|
28
|
+
#: Canonical project/repository URL.
|
|
29
|
+
__url__: str = 'https://github.com/WoLpH/python-utils'
|
|
9
30
|
|
|
31
|
+
try:
|
|
32
|
+
# `[project].version` in pyproject.toml is the single source of truth;
|
|
33
|
+
# read it back at runtime so the two never drift.
|
|
34
|
+
__version__: str = metadata.version(__package_name__)
|
|
35
|
+
except metadata.PackageNotFoundError: # pragma: no cover
|
|
36
|
+
# Not installed (e.g. running straight from a source checkout).
|
|
37
|
+
__version__ = '0.0.0'
|
python_utils/__init__.py
CHANGED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module initializes the `python_utils` package by importing various
|
|
3
|
+
submodules and functions.
|
|
4
|
+
|
|
5
|
+
Imports are performed lazily (PEP 562): nothing is imported when you ``import
|
|
6
|
+
python_utils``; each submodule/function is loaded on first access. This keeps
|
|
7
|
+
``import python_utils`` cheap and, in particular, avoids eagerly importing
|
|
8
|
+
``asyncio`` (via the async helpers) for consumers that only need the
|
|
9
|
+
synchronous utilities.
|
|
10
|
+
|
|
11
|
+
Submodules::
|
|
12
|
+
|
|
13
|
+
aio
|
|
14
|
+
converters
|
|
15
|
+
decorators
|
|
16
|
+
formatters
|
|
17
|
+
generators
|
|
18
|
+
import_
|
|
19
|
+
logger
|
|
20
|
+
terminal
|
|
21
|
+
time
|
|
22
|
+
types
|
|
23
|
+
|
|
24
|
+
Functions::
|
|
25
|
+
|
|
26
|
+
acount
|
|
27
|
+
remap
|
|
28
|
+
scale_1024
|
|
29
|
+
to_float
|
|
30
|
+
to_int
|
|
31
|
+
to_str
|
|
32
|
+
to_unicode
|
|
33
|
+
listify
|
|
34
|
+
set_attributes
|
|
35
|
+
raise_exception
|
|
36
|
+
reraise
|
|
37
|
+
camel_to_underscore
|
|
38
|
+
timesince
|
|
39
|
+
abatcher
|
|
40
|
+
batcher
|
|
41
|
+
import_global
|
|
42
|
+
get_terminal_size
|
|
43
|
+
aio_generator_timeout_detector
|
|
44
|
+
aio_generator_timeout_detector_decorator
|
|
45
|
+
aio_timeout_generator
|
|
46
|
+
delta_to_seconds
|
|
47
|
+
delta_to_seconds_or_none
|
|
48
|
+
format_time
|
|
49
|
+
timedelta_to_seconds
|
|
50
|
+
timeout_generator
|
|
51
|
+
|
|
52
|
+
Classes::
|
|
53
|
+
|
|
54
|
+
CastedDict
|
|
55
|
+
LazyCastedDict
|
|
56
|
+
UniqueList
|
|
57
|
+
Logged
|
|
58
|
+
LoggerBase
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
import importlib as _importlib
|
|
62
|
+
import typing as _typing
|
|
63
|
+
|
|
64
|
+
if _typing.TYPE_CHECKING: # pragma: no cover
|
|
65
|
+
# Eager imports for type checkers only; the runtime equivalents are loaded
|
|
66
|
+
# lazily by ``__getattr__`` below. Names appear in ``__all__`` so they are
|
|
67
|
+
# treated as re-exports (not unused imports).
|
|
68
|
+
from . import (
|
|
69
|
+
aio,
|
|
70
|
+
converters,
|
|
71
|
+
decorators,
|
|
72
|
+
formatters,
|
|
73
|
+
generators,
|
|
74
|
+
import_,
|
|
75
|
+
logger,
|
|
76
|
+
terminal,
|
|
77
|
+
time,
|
|
78
|
+
types,
|
|
79
|
+
)
|
|
80
|
+
from .__about__ import __version__
|
|
81
|
+
from .aio import acount
|
|
82
|
+
from .containers import CastedDict, LazyCastedDict, UniqueList
|
|
83
|
+
from .converters import (
|
|
84
|
+
remap,
|
|
85
|
+
scale_1024,
|
|
86
|
+
to_float,
|
|
87
|
+
to_int,
|
|
88
|
+
to_str,
|
|
89
|
+
to_unicode,
|
|
90
|
+
)
|
|
91
|
+
from .decorators import listify, set_attributes
|
|
92
|
+
from .exceptions import raise_exception, reraise
|
|
93
|
+
from .formatters import camel_to_underscore, timesince
|
|
94
|
+
from .generators import abatcher, batcher
|
|
95
|
+
from .import_ import import_global
|
|
96
|
+
from .logger import Logged, LoggerBase
|
|
97
|
+
from .terminal import get_terminal_size
|
|
98
|
+
from .time import (
|
|
99
|
+
aio_generator_timeout_detector,
|
|
100
|
+
aio_generator_timeout_detector_decorator,
|
|
101
|
+
aio_timeout_generator,
|
|
102
|
+
delta_to_seconds,
|
|
103
|
+
delta_to_seconds_or_none,
|
|
104
|
+
format_time,
|
|
105
|
+
timedelta_to_seconds,
|
|
106
|
+
timeout_generator,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
#: Submodules that can be accessed as ``python_utils.<name>``.
|
|
110
|
+
_SUBMODULES: frozenset[str] = frozenset(
|
|
111
|
+
{
|
|
112
|
+
'aio',
|
|
113
|
+
'containers',
|
|
114
|
+
'converters',
|
|
115
|
+
'decorators',
|
|
116
|
+
'exceptions',
|
|
117
|
+
'formatters',
|
|
118
|
+
'generators',
|
|
119
|
+
'import_',
|
|
120
|
+
'logger',
|
|
121
|
+
'terminal',
|
|
122
|
+
'time',
|
|
123
|
+
'types',
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
#: Exported name -> submodule it lives in.
|
|
128
|
+
_NAME_TO_MODULE: dict[str, str] = {
|
|
129
|
+
'__version__': '__about__',
|
|
130
|
+
'acount': 'aio',
|
|
131
|
+
'CastedDict': 'containers',
|
|
132
|
+
'LazyCastedDict': 'containers',
|
|
133
|
+
'UniqueList': 'containers',
|
|
134
|
+
'remap': 'converters',
|
|
135
|
+
'scale_1024': 'converters',
|
|
136
|
+
'to_float': 'converters',
|
|
137
|
+
'to_int': 'converters',
|
|
138
|
+
'to_str': 'converters',
|
|
139
|
+
'to_unicode': 'converters',
|
|
140
|
+
'listify': 'decorators',
|
|
141
|
+
'set_attributes': 'decorators',
|
|
142
|
+
'raise_exception': 'exceptions',
|
|
143
|
+
'reraise': 'exceptions',
|
|
144
|
+
'camel_to_underscore': 'formatters',
|
|
145
|
+
'timesince': 'formatters',
|
|
146
|
+
'abatcher': 'generators',
|
|
147
|
+
'batcher': 'generators',
|
|
148
|
+
'import_global': 'import_',
|
|
149
|
+
'Logged': 'logger',
|
|
150
|
+
'LoggerBase': 'logger',
|
|
151
|
+
'get_terminal_size': 'terminal',
|
|
152
|
+
'aio_generator_timeout_detector': 'time',
|
|
153
|
+
'aio_generator_timeout_detector_decorator': 'time',
|
|
154
|
+
'aio_timeout_generator': 'time',
|
|
155
|
+
'delta_to_seconds': 'time',
|
|
156
|
+
'delta_to_seconds_or_none': 'time',
|
|
157
|
+
'format_time': 'time',
|
|
158
|
+
'timedelta_to_seconds': 'time',
|
|
159
|
+
'timeout_generator': 'time',
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def __getattr__(name: str) -> _typing.Any:
|
|
164
|
+
"""Lazily import a submodule or exported name on first access (PEP 562).
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
name: Attribute requested on the ``python_utils`` package.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
The imported submodule or object. It is cached in ``globals()`` so
|
|
171
|
+
this hook runs only once per name.
|
|
172
|
+
|
|
173
|
+
Raises:
|
|
174
|
+
AttributeError: If ``name`` is not a known submodule or export.
|
|
175
|
+
"""
|
|
176
|
+
if name in _SUBMODULES:
|
|
177
|
+
module = _importlib.import_module(f'.{name}', __name__)
|
|
178
|
+
elif name in _NAME_TO_MODULE:
|
|
179
|
+
module = _importlib.import_module(
|
|
180
|
+
f'.{_NAME_TO_MODULE[name]}', __name__
|
|
181
|
+
)
|
|
182
|
+
value = getattr(module, name)
|
|
183
|
+
globals()[name] = value # cache so __getattr__ runs only once
|
|
184
|
+
return value
|
|
185
|
+
else:
|
|
186
|
+
raise AttributeError(f'module {__name__!r} has no attribute {name!r}')
|
|
187
|
+
|
|
188
|
+
globals()[name] = module
|
|
189
|
+
return module
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def __dir__() -> list[str]:
|
|
193
|
+
"""List all eager and lazily-available names (for tab-completion)."""
|
|
194
|
+
return sorted(
|
|
195
|
+
set(globals()) | set(__all__) | _SUBMODULES | set(_NAME_TO_MODULE)
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
__all__ = [
|
|
200
|
+
'CastedDict',
|
|
201
|
+
'LazyCastedDict',
|
|
202
|
+
'Logged',
|
|
203
|
+
'LoggerBase',
|
|
204
|
+
'UniqueList',
|
|
205
|
+
'__version__',
|
|
206
|
+
'abatcher',
|
|
207
|
+
'acount',
|
|
208
|
+
'aio',
|
|
209
|
+
'aio_generator_timeout_detector',
|
|
210
|
+
'aio_generator_timeout_detector_decorator',
|
|
211
|
+
'aio_timeout_generator',
|
|
212
|
+
'batcher',
|
|
213
|
+
'camel_to_underscore',
|
|
214
|
+
'converters',
|
|
215
|
+
'decorators',
|
|
216
|
+
'delta_to_seconds',
|
|
217
|
+
'delta_to_seconds_or_none',
|
|
218
|
+
'format_time',
|
|
219
|
+
'formatters',
|
|
220
|
+
'generators',
|
|
221
|
+
'get_terminal_size',
|
|
222
|
+
'import_',
|
|
223
|
+
'import_global',
|
|
224
|
+
'listify',
|
|
225
|
+
'logger',
|
|
226
|
+
'raise_exception',
|
|
227
|
+
'remap',
|
|
228
|
+
'reraise',
|
|
229
|
+
'scale_1024',
|
|
230
|
+
'set_attributes',
|
|
231
|
+
'terminal',
|
|
232
|
+
'time',
|
|
233
|
+
'timedelta_to_seconds',
|
|
234
|
+
'timeout_generator',
|
|
235
|
+
'timesince',
|
|
236
|
+
'to_float',
|
|
237
|
+
'to_int',
|
|
238
|
+
'to_str',
|
|
239
|
+
'to_unicode',
|
|
240
|
+
'types',
|
|
241
|
+
]
|
python_utils/_aliases.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Lightweight, stdlib-only type aliases shared across python_utils.
|
|
2
|
+
|
|
3
|
+
These live here (rather than in ``python_utils.types``) so internal modules can
|
|
4
|
+
import them without dragging in ``typing_extensions``. ``python_utils.types``
|
|
5
|
+
re-exports everything defined here, so the public names are unchanged.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import datetime
|
|
11
|
+
import decimal
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
'DecimalNumber',
|
|
16
|
+
'ExceptionType',
|
|
17
|
+
'ExceptionsType',
|
|
18
|
+
'Number',
|
|
19
|
+
'OptionalScope',
|
|
20
|
+
'Scope',
|
|
21
|
+
'StringTypes',
|
|
22
|
+
'delta_type',
|
|
23
|
+
'timestamp_type',
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
#: A namespace mapping, e.g. ``locals()``/``globals()`` (name -> value).
|
|
27
|
+
Scope = dict[str, Any]
|
|
28
|
+
#: A :data:`Scope`, or ``None`` when no namespace is supplied.
|
|
29
|
+
OptionalScope = Scope | None
|
|
30
|
+
#: Any plain (non-decimal) number: an ``int`` or a ``float``.
|
|
31
|
+
Number = int | float
|
|
32
|
+
#: A :data:`Number` or a :class:`decimal.Decimal`, for precise arithmetic.
|
|
33
|
+
DecimalNumber = Number | decimal.Decimal
|
|
34
|
+
#: An exception class (not an instance), e.g. ``ValueError``.
|
|
35
|
+
ExceptionType = type[Exception]
|
|
36
|
+
#: One exception class or a tuple of them, as accepted by ``except``.
|
|
37
|
+
ExceptionsType = tuple[ExceptionType, ...] | ExceptionType
|
|
38
|
+
#: Text-like data: ``str`` or ``bytes``.
|
|
39
|
+
StringTypes = str | bytes
|
|
40
|
+
|
|
41
|
+
#: A time interval expressed as a ``timedelta`` or a number of seconds.
|
|
42
|
+
delta_type = datetime.timedelta | int | float
|
|
43
|
+
#: Anything :func:`~python_utils.time.format_time` can render: a duration, a
|
|
44
|
+
#: date/datetime, a numeric/str timestamp, or ``None``.
|
|
45
|
+
timestamp_type = (
|
|
46
|
+
datetime.timedelta
|
|
47
|
+
| datetime.date
|
|
48
|
+
| datetime.datetime
|
|
49
|
+
| str
|
|
50
|
+
| int
|
|
51
|
+
| float
|
|
52
|
+
| None
|
|
53
|
+
)
|
python_utils/aio.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Asyncio equivalents of common synchronous helpers.
|
|
2
|
+
|
|
3
|
+
These bring ``itertools``-style ergonomics to ``async for``: ``acount`` is an
|
|
4
|
+
async counter, while ``acontainer`` and ``adict`` collect an async iterable
|
|
5
|
+
into a concrete container.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import collections.abc
|
|
10
|
+
import itertools
|
|
11
|
+
import typing
|
|
12
|
+
|
|
13
|
+
#: Numeric type (``int`` or ``float``) produced by ``acount``.
|
|
14
|
+
_N = typing.TypeVar('_N', int, float)
|
|
15
|
+
#: Element type of the async iterables being consumed.
|
|
16
|
+
_T = typing.TypeVar('_T')
|
|
17
|
+
#: Key type when collecting an async iterable of pairs into a dict.
|
|
18
|
+
_K = typing.TypeVar('_K')
|
|
19
|
+
#: Value type when collecting an async iterable of pairs into a dict.
|
|
20
|
+
_V = typing.TypeVar('_V')
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def acount(
|
|
24
|
+
start: _N = 0,
|
|
25
|
+
step: _N = 1,
|
|
26
|
+
delay: float = 0,
|
|
27
|
+
stop: _N | None = None,
|
|
28
|
+
) -> collections.abc.AsyncIterator[_N]:
|
|
29
|
+
"""Async equivalent of ``itertools.count()``.
|
|
30
|
+
|
|
31
|
+
Counts from ``start`` in steps of ``step``, sleeping ``delay`` seconds
|
|
32
|
+
between values, and stops once ``stop`` (when given) is reached.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
start: First value to yield.
|
|
36
|
+
step: Amount added between successive values.
|
|
37
|
+
delay: Seconds to ``asyncio.sleep`` between yields.
|
|
38
|
+
stop: Exclusive upper bound; ``None`` counts forever.
|
|
39
|
+
|
|
40
|
+
Yields:
|
|
41
|
+
The successive counter values.
|
|
42
|
+
|
|
43
|
+
>>> async def demo():
|
|
44
|
+
... return [i async for i in acount(stop=3)]
|
|
45
|
+
>>> asyncio.run(demo())
|
|
46
|
+
[0, 1, 2]
|
|
47
|
+
"""
|
|
48
|
+
for item in itertools.count(start, step): # pragma: no branch
|
|
49
|
+
if stop is not None and item >= stop:
|
|
50
|
+
break
|
|
51
|
+
|
|
52
|
+
yield item
|
|
53
|
+
await asyncio.sleep(delay)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@typing.overload
|
|
57
|
+
async def acontainer(
|
|
58
|
+
iterable: collections.abc.AsyncIterable[_T]
|
|
59
|
+
| collections.abc.Callable[..., collections.abc.AsyncIterable[_T]],
|
|
60
|
+
container: type[tuple[_T, ...]],
|
|
61
|
+
) -> tuple[_T, ...]:
|
|
62
|
+
"""Overload: collect the async iterable into a ``tuple``."""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@typing.overload
|
|
66
|
+
async def acontainer(
|
|
67
|
+
iterable: collections.abc.AsyncIterable[_T]
|
|
68
|
+
| collections.abc.Callable[..., collections.abc.AsyncIterable[_T]],
|
|
69
|
+
container: type[list[_T]] = list,
|
|
70
|
+
) -> list[_T]:
|
|
71
|
+
"""Overload: collect the async iterable into a ``list`` (the default)."""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@typing.overload
|
|
75
|
+
async def acontainer(
|
|
76
|
+
iterable: collections.abc.AsyncIterable[_T]
|
|
77
|
+
| collections.abc.Callable[..., collections.abc.AsyncIterable[_T]],
|
|
78
|
+
container: type[set[_T]],
|
|
79
|
+
) -> set[_T]:
|
|
80
|
+
"""Overload: collect the async iterable into a ``set``."""
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
async def acontainer(
|
|
84
|
+
iterable: collections.abc.AsyncIterable[_T]
|
|
85
|
+
| collections.abc.Callable[..., collections.abc.AsyncIterable[_T]],
|
|
86
|
+
container: collections.abc.Callable[
|
|
87
|
+
[collections.abc.Iterable[_T]], collections.abc.Collection[_T]
|
|
88
|
+
] = list,
|
|
89
|
+
) -> collections.abc.Collection[_T]:
|
|
90
|
+
"""
|
|
91
|
+
Asyncio version of list()/set()/tuple()/etc() using an async for loop.
|
|
92
|
+
|
|
93
|
+
So instead of doing `[item async for item in iterable]` you can do
|
|
94
|
+
`await acontainer(iterable)`.
|
|
95
|
+
|
|
96
|
+
"""
|
|
97
|
+
iterable_: collections.abc.AsyncIterable[_T]
|
|
98
|
+
if callable(iterable):
|
|
99
|
+
iterable_ = iterable()
|
|
100
|
+
else:
|
|
101
|
+
iterable_ = iterable
|
|
102
|
+
|
|
103
|
+
items: list[_T] = [item async for item in iterable_]
|
|
104
|
+
|
|
105
|
+
return container(items)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
async def adict(
|
|
109
|
+
iterable: collections.abc.AsyncIterable[tuple[_K, _V]]
|
|
110
|
+
| collections.abc.Callable[
|
|
111
|
+
..., collections.abc.AsyncIterable[tuple[_K, _V]]
|
|
112
|
+
],
|
|
113
|
+
container: collections.abc.Callable[
|
|
114
|
+
[collections.abc.Iterable[tuple[_K, _V]]],
|
|
115
|
+
collections.abc.Mapping[_K, _V],
|
|
116
|
+
] = dict,
|
|
117
|
+
) -> collections.abc.Mapping[_K, _V]:
|
|
118
|
+
"""
|
|
119
|
+
Asyncio version of dict() using an async for loop.
|
|
120
|
+
|
|
121
|
+
So instead of doing `{key: value async for key, value in iterable}` you
|
|
122
|
+
can do `await adict(iterable)`.
|
|
123
|
+
|
|
124
|
+
"""
|
|
125
|
+
iterable_: collections.abc.AsyncIterable[tuple[_K, _V]]
|
|
126
|
+
if callable(iterable):
|
|
127
|
+
iterable_ = iterable()
|
|
128
|
+
else:
|
|
129
|
+
iterable_ = iterable
|
|
130
|
+
|
|
131
|
+
items: list[tuple[_K, _V]] = [item async for item in iterable_]
|
|
132
|
+
|
|
133
|
+
return container(items)
|