dycw-utilities 0.148.5__py3-none-any.whl → 0.174.12__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.
Potentially problematic release.
This version of dycw-utilities might be problematic. Click here for more details.
- dycw_utilities-0.174.12.dist-info/METADATA +41 -0
- dycw_utilities-0.174.12.dist-info/RECORD +104 -0
- dycw_utilities-0.174.12.dist-info/WHEEL +4 -0
- {dycw_utilities-0.148.5.dist-info → dycw_utilities-0.174.12.dist-info}/entry_points.txt +3 -0
- utilities/__init__.py +1 -1
- utilities/{eventkit.py → aeventkit.py} +12 -11
- utilities/altair.py +7 -6
- utilities/asyncio.py +113 -64
- utilities/atomicwrites.py +1 -1
- utilities/atools.py +64 -4
- utilities/cachetools.py +9 -6
- utilities/click.py +145 -49
- utilities/concurrent.py +1 -1
- utilities/contextlib.py +4 -2
- utilities/contextvars.py +20 -1
- utilities/cryptography.py +3 -3
- utilities/dataclasses.py +15 -28
- utilities/docker.py +292 -0
- utilities/enum.py +2 -2
- utilities/errors.py +1 -1
- utilities/fastapi.py +8 -3
- utilities/fpdf2.py +2 -2
- utilities/functions.py +20 -297
- utilities/git.py +19 -0
- utilities/grp.py +28 -0
- utilities/hypothesis.py +360 -78
- utilities/inflect.py +1 -1
- utilities/iterables.py +12 -58
- utilities/jinja2.py +148 -0
- utilities/json.py +1 -1
- utilities/libcst.py +7 -7
- utilities/logging.py +74 -85
- utilities/math.py +8 -4
- utilities/more_itertools.py +4 -6
- utilities/operator.py +1 -1
- utilities/orjson.py +86 -34
- utilities/os.py +49 -2
- utilities/parse.py +2 -2
- utilities/pathlib.py +66 -34
- utilities/permissions.py +297 -0
- utilities/platform.py +5 -5
- utilities/polars.py +932 -420
- utilities/polars_ols.py +1 -1
- utilities/postgres.py +296 -174
- utilities/pottery.py +8 -73
- utilities/pqdm.py +3 -3
- utilities/pwd.py +28 -0
- utilities/pydantic.py +11 -0
- utilities/pydantic_settings.py +240 -0
- utilities/pydantic_settings_sops.py +76 -0
- utilities/pyinstrument.py +5 -5
- utilities/pytest.py +155 -46
- utilities/pytest_plugins/pytest_randomly.py +1 -1
- utilities/pytest_plugins/pytest_regressions.py +7 -3
- utilities/pytest_regressions.py +2 -3
- utilities/random.py +11 -6
- utilities/re.py +1 -1
- utilities/redis.py +101 -64
- utilities/sentinel.py +10 -0
- utilities/shelve.py +4 -1
- utilities/shutil.py +25 -0
- utilities/slack_sdk.py +8 -3
- utilities/sqlalchemy.py +422 -352
- utilities/sqlalchemy_polars.py +28 -52
- utilities/string.py +1 -1
- utilities/subprocess.py +864 -0
- utilities/tempfile.py +62 -4
- utilities/testbook.py +50 -0
- utilities/text.py +165 -42
- utilities/timer.py +2 -2
- utilities/traceback.py +46 -36
- utilities/types.py +62 -23
- utilities/typing.py +479 -19
- utilities/uuid.py +42 -5
- utilities/version.py +27 -26
- utilities/whenever.py +661 -151
- utilities/zoneinfo.py +80 -22
- dycw_utilities-0.148.5.dist-info/METADATA +0 -41
- dycw_utilities-0.148.5.dist-info/RECORD +0 -95
- dycw_utilities-0.148.5.dist-info/WHEEL +0 -4
- dycw_utilities-0.148.5.dist-info/licenses/LICENSE +0 -21
- utilities/period.py +0 -237
- utilities/typed_settings.py +0 -144
utilities/inflect.py
CHANGED
utilities/iterables.py
CHANGED
|
@@ -18,7 +18,7 @@ from enum import Enum
|
|
|
18
18
|
from functools import cmp_to_key, partial, reduce
|
|
19
19
|
from itertools import accumulate, chain, groupby, islice, pairwise, product
|
|
20
20
|
from math import isnan
|
|
21
|
-
from operator import add,
|
|
21
|
+
from operator import add, or_
|
|
22
22
|
from typing import (
|
|
23
23
|
TYPE_CHECKING,
|
|
24
24
|
Any,
|
|
@@ -31,7 +31,6 @@ from typing import (
|
|
|
31
31
|
)
|
|
32
32
|
|
|
33
33
|
from utilities.errors import ImpossibleCaseError
|
|
34
|
-
from utilities.functions import ensure_hashable, ensure_str
|
|
35
34
|
from utilities.math import (
|
|
36
35
|
_CheckIntegerEqualError,
|
|
37
36
|
_CheckIntegerEqualOrApproxError,
|
|
@@ -40,13 +39,13 @@ from utilities.math import (
|
|
|
40
39
|
check_integer,
|
|
41
40
|
)
|
|
42
41
|
from utilities.reprlib import get_repr
|
|
43
|
-
from utilities.sentinel import Sentinel, sentinel
|
|
42
|
+
from utilities.sentinel import Sentinel, is_sentinel, sentinel
|
|
44
43
|
from utilities.types import SupportsAdd, SupportsLT
|
|
45
44
|
|
|
46
45
|
if TYPE_CHECKING:
|
|
47
46
|
from types import NoneType
|
|
48
47
|
|
|
49
|
-
from utilities.types import MaybeIterable,
|
|
48
|
+
from utilities.types import MaybeIterable, Sign, StrMapping
|
|
50
49
|
|
|
51
50
|
|
|
52
51
|
##
|
|
@@ -66,16 +65,6 @@ def always_iterable[T](obj: MaybeIterable[T], /) -> Iterable[T]:
|
|
|
66
65
|
##
|
|
67
66
|
|
|
68
67
|
|
|
69
|
-
def always_iterable_hashable[T](
|
|
70
|
-
obj: MaybeIterable[T] | None, /
|
|
71
|
-
) -> MaybeIterableHashable[T] | None:
|
|
72
|
-
"""Ensure an object is always hashable."""
|
|
73
|
-
return None if obj is None else tuple(always_iterable(obj))
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
##
|
|
77
|
-
|
|
78
|
-
|
|
79
68
|
def apply_bijection[T, U](
|
|
80
69
|
func: Callable[[T], U], iterable: Iterable[T], /
|
|
81
70
|
) -> Mapping[T, U]:
|
|
@@ -246,8 +235,7 @@ def check_iterables_equal(left: Iterable[Any], right: Iterable[Any], /) -> None:
|
|
|
246
235
|
if lv != rv:
|
|
247
236
|
errors.append((i, lv, rv))
|
|
248
237
|
except ValueError as error:
|
|
249
|
-
|
|
250
|
-
match msg:
|
|
238
|
+
match one(error.args):
|
|
251
239
|
case "zip() argument 2 is longer than argument 1":
|
|
252
240
|
state = "right_longer"
|
|
253
241
|
case "zip() argument 2 is shorter than argument 1":
|
|
@@ -295,7 +283,7 @@ class CheckIterablesEqualError[T](Exception):
|
|
|
295
283
|
yield "right was longer"
|
|
296
284
|
case None:
|
|
297
285
|
pass
|
|
298
|
-
case
|
|
286
|
+
case never:
|
|
299
287
|
assert_never(never)
|
|
300
288
|
|
|
301
289
|
|
|
@@ -690,7 +678,7 @@ def cmp_nullable[T: SupportsLT](x: T | None, y: T | None, /) -> Sign:
|
|
|
690
678
|
return 1
|
|
691
679
|
case _, _:
|
|
692
680
|
return cast("Sign", (x > y) - (x < y))
|
|
693
|
-
case
|
|
681
|
+
case never:
|
|
694
682
|
assert_never(never)
|
|
695
683
|
|
|
696
684
|
|
|
@@ -705,18 +693,6 @@ def chunked[T](iterable: Iterable[T], n: int, /) -> Iterator[Sequence[T]]:
|
|
|
705
693
|
##
|
|
706
694
|
|
|
707
695
|
|
|
708
|
-
def ensure_hashables(
|
|
709
|
-
*args: Any, **kwargs: Any
|
|
710
|
-
) -> tuple[list[Hashable], dict[str, Hashable]]:
|
|
711
|
-
"""Ensure a set of positional & keyword arguments are all hashable."""
|
|
712
|
-
hash_args = list(map(ensure_hashable, args))
|
|
713
|
-
hash_kwargs = {k: ensure_hashable(v) for k, v in kwargs.items()}
|
|
714
|
-
return hash_args, hash_kwargs
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
##
|
|
718
|
-
|
|
719
|
-
|
|
720
696
|
def ensure_iterable(obj: Any, /) -> Iterable[Any]:
|
|
721
697
|
"""Ensure an object is iterable."""
|
|
722
698
|
if is_iterable(obj):
|
|
@@ -831,24 +807,6 @@ def filter_include_and_exclude[T, U](
|
|
|
831
807
|
##
|
|
832
808
|
|
|
833
809
|
|
|
834
|
-
def group_consecutive_integers(iterable: Iterable[int], /) -> Iterable[tuple[int, int]]:
|
|
835
|
-
"""Group consecutive integers."""
|
|
836
|
-
integers = sorted(iterable)
|
|
837
|
-
for _, group in groupby(enumerate(integers), key=lambda x: x[1] - x[0]):
|
|
838
|
-
as_list = list(map(itemgetter(1), group))
|
|
839
|
-
yield as_list[0], as_list[-1]
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
def ungroup_consecutive_integers(
|
|
843
|
-
iterable: Iterable[tuple[int, int]], /
|
|
844
|
-
) -> Iterable[int]:
|
|
845
|
-
"""Ungroup consecutive integers."""
|
|
846
|
-
return chain.from_iterable(range(start, end + 1) for start, end in iterable)
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
##
|
|
850
|
-
|
|
851
|
-
|
|
852
810
|
@overload
|
|
853
811
|
def groupby_lists[T](
|
|
854
812
|
iterable: Iterable[T], /, *, key: None = None
|
|
@@ -975,7 +933,7 @@ class MergeStrMappingsError(Exception):
|
|
|
975
933
|
|
|
976
934
|
def one[T](*iterables: Iterable[T]) -> T:
|
|
977
935
|
"""Return the unique value in a set of iterables."""
|
|
978
|
-
it =
|
|
936
|
+
it = chain(*iterables)
|
|
979
937
|
try:
|
|
980
938
|
first = next(it)
|
|
981
939
|
except StopIteration:
|
|
@@ -1068,7 +1026,7 @@ def one_str(
|
|
|
1068
1026
|
it = (t for t in as_list if t.startswith(text))
|
|
1069
1027
|
case True, False:
|
|
1070
1028
|
it = (t for t in as_list if t.lower().startswith(text.lower()))
|
|
1071
|
-
case
|
|
1029
|
+
case never:
|
|
1072
1030
|
assert_never(never)
|
|
1073
1031
|
try:
|
|
1074
1032
|
return one(it)
|
|
@@ -1109,7 +1067,7 @@ class OneStrEmptyError(OneStrError):
|
|
|
1109
1067
|
tail = f"any string starting with {self.text!r}"
|
|
1110
1068
|
case True, False:
|
|
1111
1069
|
tail = f"any string starting with {self.text!r} (modulo case)"
|
|
1112
|
-
case
|
|
1070
|
+
case never:
|
|
1113
1071
|
assert_never(never)
|
|
1114
1072
|
return f"{head} {tail}"
|
|
1115
1073
|
|
|
@@ -1131,7 +1089,7 @@ class OneStrNonUniqueError(OneStrError):
|
|
|
1131
1089
|
mid = f"exactly one string starting with {self.text!r}"
|
|
1132
1090
|
case True, False:
|
|
1133
1091
|
mid = f"exactly one string starting with {self.text!r} (modulo case)"
|
|
1134
|
-
case
|
|
1092
|
+
case never:
|
|
1135
1093
|
assert_never(never)
|
|
1136
1094
|
return f"{head} {mid}; got {self.first!r}, {self.second!r} and perhaps more"
|
|
1137
1095
|
|
|
@@ -1265,7 +1223,7 @@ def reduce_mappings[K, V, W](
|
|
|
1265
1223
|
) -> Mapping[K, V | W]:
|
|
1266
1224
|
"""Reduce a function over the values of a set of mappings."""
|
|
1267
1225
|
chained = chain_mappings(*sequence)
|
|
1268
|
-
if
|
|
1226
|
+
if is_sentinel(initial):
|
|
1269
1227
|
func2 = cast("Callable[[V, V], V]", func)
|
|
1270
1228
|
return {k: reduce(func2, v) for k, v in chained.items()}
|
|
1271
1229
|
func2 = cast("Callable[[W, V], W]", func)
|
|
@@ -1382,7 +1340,7 @@ def _sort_iterable_cmp_floats(x: float, y: float, /) -> Sign:
|
|
|
1382
1340
|
return -1
|
|
1383
1341
|
case False, False:
|
|
1384
1342
|
return cast("Sign", (x > y) - (x < y))
|
|
1385
|
-
case
|
|
1343
|
+
case never:
|
|
1386
1344
|
assert_never(never)
|
|
1387
1345
|
|
|
1388
1346
|
|
|
@@ -1489,7 +1447,6 @@ __all__ = [
|
|
|
1489
1447
|
"ResolveIncludeAndExcludeError",
|
|
1490
1448
|
"SortIterableError",
|
|
1491
1449
|
"always_iterable",
|
|
1492
|
-
"always_iterable_hashable",
|
|
1493
1450
|
"apply_bijection",
|
|
1494
1451
|
"apply_to_tuple",
|
|
1495
1452
|
"apply_to_varargs",
|
|
@@ -1509,13 +1466,11 @@ __all__ = [
|
|
|
1509
1466
|
"check_unique_modulo_case",
|
|
1510
1467
|
"chunked",
|
|
1511
1468
|
"cmp_nullable",
|
|
1512
|
-
"ensure_hashables",
|
|
1513
1469
|
"ensure_iterable",
|
|
1514
1470
|
"ensure_iterable_not_str",
|
|
1515
1471
|
"enumerate_with_edge",
|
|
1516
1472
|
"expanding_window",
|
|
1517
1473
|
"filter_include_and_exclude",
|
|
1518
|
-
"group_consecutive_integers",
|
|
1519
1474
|
"groupby_lists",
|
|
1520
1475
|
"hashable_to_iterable",
|
|
1521
1476
|
"is_iterable",
|
|
@@ -1538,6 +1493,5 @@ __all__ = [
|
|
|
1538
1493
|
"sum_mappings",
|
|
1539
1494
|
"take",
|
|
1540
1495
|
"transpose",
|
|
1541
|
-
"ungroup_consecutive_integers",
|
|
1542
1496
|
"unique_everseen",
|
|
1543
1497
|
]
|
utilities/jinja2.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Literal, assert_never, override
|
|
5
|
+
|
|
6
|
+
from jinja2 import BaseLoader, BytecodeCache, Environment, FileSystemLoader, Undefined
|
|
7
|
+
from jinja2.defaults import (
|
|
8
|
+
BLOCK_END_STRING,
|
|
9
|
+
BLOCK_START_STRING,
|
|
10
|
+
COMMENT_END_STRING,
|
|
11
|
+
COMMENT_START_STRING,
|
|
12
|
+
KEEP_TRAILING_NEWLINE,
|
|
13
|
+
LINE_COMMENT_PREFIX,
|
|
14
|
+
LINE_STATEMENT_PREFIX,
|
|
15
|
+
LSTRIP_BLOCKS,
|
|
16
|
+
NEWLINE_SEQUENCE,
|
|
17
|
+
TRIM_BLOCKS,
|
|
18
|
+
VARIABLE_END_STRING,
|
|
19
|
+
VARIABLE_START_STRING,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from utilities.atomicwrites import writer
|
|
23
|
+
from utilities.text import kebab_case, pascal_case, snake_case
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from collections.abc import Callable, Sequence
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
from jinja2.ext import Extension
|
|
30
|
+
|
|
31
|
+
from utilities.types import StrMapping
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class EnhancedEnvironment(Environment):
|
|
35
|
+
"""Environment with enhanced features."""
|
|
36
|
+
|
|
37
|
+
@override
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
block_start_string: str = BLOCK_START_STRING,
|
|
41
|
+
block_end_string: str = BLOCK_END_STRING,
|
|
42
|
+
variable_start_string: str = VARIABLE_START_STRING,
|
|
43
|
+
variable_end_string: str = VARIABLE_END_STRING,
|
|
44
|
+
comment_start_string: str = COMMENT_START_STRING,
|
|
45
|
+
comment_end_string: str = COMMENT_END_STRING,
|
|
46
|
+
line_statement_prefix: str | None = LINE_STATEMENT_PREFIX,
|
|
47
|
+
line_comment_prefix: str | None = LINE_COMMENT_PREFIX,
|
|
48
|
+
trim_blocks: bool = TRIM_BLOCKS,
|
|
49
|
+
lstrip_blocks: bool = LSTRIP_BLOCKS,
|
|
50
|
+
newline_sequence: Literal["\n", "\r\n", "\r"] = NEWLINE_SEQUENCE,
|
|
51
|
+
keep_trailing_newline: bool = KEEP_TRAILING_NEWLINE,
|
|
52
|
+
extensions: Sequence[str | type[Extension]] = (),
|
|
53
|
+
optimized: bool = True,
|
|
54
|
+
undefined: type[Undefined] = Undefined,
|
|
55
|
+
finalize: Callable[..., Any] | None = None,
|
|
56
|
+
autoescape: bool | Callable[[str | None], bool] = False,
|
|
57
|
+
loader: BaseLoader | None = None,
|
|
58
|
+
cache_size: int = 400,
|
|
59
|
+
auto_reload: bool = True,
|
|
60
|
+
bytecode_cache: BytecodeCache | None = None,
|
|
61
|
+
enable_async: bool = False,
|
|
62
|
+
) -> None:
|
|
63
|
+
super().__init__(
|
|
64
|
+
block_start_string,
|
|
65
|
+
block_end_string,
|
|
66
|
+
variable_start_string,
|
|
67
|
+
variable_end_string,
|
|
68
|
+
comment_start_string,
|
|
69
|
+
comment_end_string,
|
|
70
|
+
line_statement_prefix,
|
|
71
|
+
line_comment_prefix,
|
|
72
|
+
trim_blocks,
|
|
73
|
+
lstrip_blocks,
|
|
74
|
+
newline_sequence,
|
|
75
|
+
keep_trailing_newline,
|
|
76
|
+
extensions,
|
|
77
|
+
optimized,
|
|
78
|
+
undefined,
|
|
79
|
+
finalize,
|
|
80
|
+
autoescape,
|
|
81
|
+
loader,
|
|
82
|
+
cache_size,
|
|
83
|
+
auto_reload,
|
|
84
|
+
bytecode_cache,
|
|
85
|
+
enable_async,
|
|
86
|
+
)
|
|
87
|
+
self.filters["kebab"] = kebab_case
|
|
88
|
+
self.filters["pascal"] = pascal_case
|
|
89
|
+
self.filters["snake"] = snake_case
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass(order=True, unsafe_hash=True, kw_only=True, slots=True)
|
|
93
|
+
class TemplateJob:
|
|
94
|
+
"""A template with an associated rendering job."""
|
|
95
|
+
|
|
96
|
+
template: Path
|
|
97
|
+
kwargs: StrMapping
|
|
98
|
+
target: Path
|
|
99
|
+
mode: Literal["write", "append"] = "write"
|
|
100
|
+
|
|
101
|
+
def __post_init__(self) -> None:
|
|
102
|
+
if not self.template.exists():
|
|
103
|
+
raise _TemplateJobTemplateDoesNotExistError(path=self.template)
|
|
104
|
+
if (self.mode == "append") and not self.target.exists():
|
|
105
|
+
raise _TemplateJobTargetDoesNotExistError(path=self.template)
|
|
106
|
+
|
|
107
|
+
def run(self) -> None:
|
|
108
|
+
"""Run the job."""
|
|
109
|
+
match self.mode:
|
|
110
|
+
case "write":
|
|
111
|
+
with writer(self.target, overwrite=True) as temp:
|
|
112
|
+
_ = temp.write_text(self.rendered)
|
|
113
|
+
case "append":
|
|
114
|
+
with self.target.open(mode="a") as fh:
|
|
115
|
+
_ = fh.write(self.rendered)
|
|
116
|
+
case never:
|
|
117
|
+
assert_never(never)
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def rendered(self) -> str:
|
|
121
|
+
"""The template, rendered."""
|
|
122
|
+
env = EnhancedEnvironment(loader=FileSystemLoader(self.template.parent))
|
|
123
|
+
return env.get_template(self.template.name).render(self.kwargs)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@dataclass(kw_only=True, slots=True)
|
|
127
|
+
class TemplateJobError(Exception): ...
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@dataclass(kw_only=True, slots=True)
|
|
131
|
+
class _TemplateJobTemplateDoesNotExistError(TemplateJobError):
|
|
132
|
+
path: Path
|
|
133
|
+
|
|
134
|
+
@override
|
|
135
|
+
def __str__(self) -> str:
|
|
136
|
+
return f"Template {str(self.path)!r} does not exist"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass(kw_only=True, slots=True)
|
|
140
|
+
class _TemplateJobTargetDoesNotExistError(TemplateJobError):
|
|
141
|
+
path: Path
|
|
142
|
+
|
|
143
|
+
@override
|
|
144
|
+
def __str__(self) -> str:
|
|
145
|
+
return f"Target {str(self.path)!r} does not exist"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
__all__ = ["EnhancedEnvironment", "TemplateJob", "TemplateJobError"]
|
utilities/json.py
CHANGED
utilities/libcst.py
CHANGED
|
@@ -54,7 +54,7 @@ def generate_import_from(
|
|
|
54
54
|
case _, str():
|
|
55
55
|
alias = ImportAlias(name=Name(name), asname=AsName(Name(asname)))
|
|
56
56
|
names = [alias]
|
|
57
|
-
case
|
|
57
|
+
case never:
|
|
58
58
|
assert_never(never)
|
|
59
59
|
return ImportFrom(module=split_dotted_str(module), names=names)
|
|
60
60
|
|
|
@@ -92,9 +92,9 @@ def parse_import(import_: Import | ImportFrom, /) -> Sequence[_ParseImportOutput
|
|
|
92
92
|
return [_parse_import_from_one(module, n) for n in names]
|
|
93
93
|
case ImportStar():
|
|
94
94
|
return [_ParseImportOutput(module=module, name="*")]
|
|
95
|
-
case
|
|
95
|
+
case never:
|
|
96
96
|
assert_never(never)
|
|
97
|
-
case
|
|
97
|
+
case never:
|
|
98
98
|
assert_never(never)
|
|
99
99
|
|
|
100
100
|
|
|
@@ -108,7 +108,7 @@ def _parse_import_from_one(module: str, alias: ImportAlias, /) -> _ParseImportOu
|
|
|
108
108
|
return _ParseImportOutput(module=module, name=name)
|
|
109
109
|
case Attribute() as attr:
|
|
110
110
|
raise _ParseImportAliasError(module=module, attr=attr)
|
|
111
|
-
case
|
|
111
|
+
case never:
|
|
112
112
|
assert_never(never)
|
|
113
113
|
|
|
114
114
|
|
|
@@ -150,7 +150,7 @@ def split_dotted_str(dotted: str, /) -> Name | Attribute:
|
|
|
150
150
|
|
|
151
151
|
def join_dotted_str(name_or_attr: Name | Attribute, /) -> str:
|
|
152
152
|
"""Join a dotted from from a name/attribute."""
|
|
153
|
-
parts:
|
|
153
|
+
parts: list[str] = []
|
|
154
154
|
curr: BaseExpression | Name | Attribute = name_or_attr
|
|
155
155
|
while True:
|
|
156
156
|
match curr:
|
|
@@ -162,7 +162,7 @@ def join_dotted_str(name_or_attr: Name | Attribute, /) -> str:
|
|
|
162
162
|
curr = value
|
|
163
163
|
case BaseExpression(): # pragma: no cover
|
|
164
164
|
raise ImpossibleCaseError(case=[f"{curr=}"])
|
|
165
|
-
case
|
|
165
|
+
case never:
|
|
166
166
|
assert_never(never)
|
|
167
167
|
return ".".join(reversed(parts))
|
|
168
168
|
|
|
@@ -180,7 +180,7 @@ def render_module(source: str | Module, /) -> str:
|
|
|
180
180
|
return text
|
|
181
181
|
case Module() as module:
|
|
182
182
|
return render_module(module.code)
|
|
183
|
-
case
|
|
183
|
+
case never:
|
|
184
184
|
assert_never(never)
|
|
185
185
|
|
|
186
186
|
|