dycw-utilities 0.135.0__py3-none-any.whl → 0.178.1__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.

Files changed (97) hide show
  1. dycw_utilities-0.178.1.dist-info/METADATA +34 -0
  2. dycw_utilities-0.178.1.dist-info/RECORD +105 -0
  3. dycw_utilities-0.178.1.dist-info/WHEEL +4 -0
  4. dycw_utilities-0.178.1.dist-info/entry_points.txt +4 -0
  5. utilities/__init__.py +1 -1
  6. utilities/altair.py +13 -10
  7. utilities/asyncio.py +312 -787
  8. utilities/atomicwrites.py +18 -6
  9. utilities/atools.py +64 -4
  10. utilities/cachetools.py +9 -6
  11. utilities/click.py +195 -77
  12. utilities/concurrent.py +1 -1
  13. utilities/contextlib.py +216 -17
  14. utilities/contextvars.py +20 -1
  15. utilities/cryptography.py +3 -3
  16. utilities/dataclasses.py +15 -28
  17. utilities/docker.py +387 -0
  18. utilities/enum.py +2 -2
  19. utilities/errors.py +17 -3
  20. utilities/fastapi.py +28 -59
  21. utilities/fpdf2.py +2 -2
  22. utilities/functions.py +24 -269
  23. utilities/git.py +9 -30
  24. utilities/grp.py +28 -0
  25. utilities/gzip.py +31 -0
  26. utilities/http.py +3 -2
  27. utilities/hypothesis.py +513 -159
  28. utilities/importlib.py +17 -1
  29. utilities/inflect.py +12 -4
  30. utilities/iterables.py +33 -58
  31. utilities/jinja2.py +148 -0
  32. utilities/json.py +70 -0
  33. utilities/libcst.py +38 -17
  34. utilities/lightweight_charts.py +4 -7
  35. utilities/logging.py +136 -93
  36. utilities/math.py +8 -4
  37. utilities/more_itertools.py +43 -45
  38. utilities/operator.py +27 -27
  39. utilities/orjson.py +189 -36
  40. utilities/os.py +61 -4
  41. utilities/packaging.py +115 -0
  42. utilities/parse.py +8 -5
  43. utilities/pathlib.py +269 -40
  44. utilities/permissions.py +298 -0
  45. utilities/platform.py +7 -6
  46. utilities/polars.py +1205 -413
  47. utilities/polars_ols.py +1 -1
  48. utilities/postgres.py +408 -0
  49. utilities/pottery.py +43 -19
  50. utilities/pqdm.py +3 -3
  51. utilities/psutil.py +5 -57
  52. utilities/pwd.py +28 -0
  53. utilities/pydantic.py +4 -52
  54. utilities/pydantic_settings.py +240 -0
  55. utilities/pydantic_settings_sops.py +76 -0
  56. utilities/pyinstrument.py +7 -7
  57. utilities/pytest.py +104 -143
  58. utilities/pytest_plugins/__init__.py +1 -0
  59. utilities/pytest_plugins/pytest_randomly.py +23 -0
  60. utilities/pytest_plugins/pytest_regressions.py +56 -0
  61. utilities/pytest_regressions.py +26 -46
  62. utilities/random.py +11 -6
  63. utilities/re.py +1 -1
  64. utilities/redis.py +220 -343
  65. utilities/sentinel.py +10 -0
  66. utilities/shelve.py +4 -1
  67. utilities/shutil.py +25 -0
  68. utilities/slack_sdk.py +35 -104
  69. utilities/sqlalchemy.py +496 -471
  70. utilities/sqlalchemy_polars.py +29 -54
  71. utilities/string.py +2 -3
  72. utilities/subprocess.py +1977 -0
  73. utilities/tempfile.py +112 -4
  74. utilities/testbook.py +50 -0
  75. utilities/text.py +174 -42
  76. utilities/throttle.py +158 -0
  77. utilities/timer.py +2 -2
  78. utilities/traceback.py +70 -35
  79. utilities/types.py +102 -30
  80. utilities/typing.py +479 -19
  81. utilities/uuid.py +42 -5
  82. utilities/version.py +27 -26
  83. utilities/whenever.py +1559 -361
  84. utilities/zoneinfo.py +80 -22
  85. dycw_utilities-0.135.0.dist-info/METADATA +0 -39
  86. dycw_utilities-0.135.0.dist-info/RECORD +0 -96
  87. dycw_utilities-0.135.0.dist-info/WHEEL +0 -4
  88. dycw_utilities-0.135.0.dist-info/licenses/LICENSE +0 -21
  89. utilities/aiolimiter.py +0 -25
  90. utilities/arq.py +0 -216
  91. utilities/eventkit.py +0 -388
  92. utilities/luigi.py +0 -183
  93. utilities/period.py +0 -152
  94. utilities/pudb.py +0 -62
  95. utilities/python_dotenv.py +0 -101
  96. utilities/streamlit.py +0 -105
  97. utilities/typed_settings.py +0 -123
utilities/packaging.py ADDED
@@ -0,0 +1,115 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import TYPE_CHECKING, Self, overload, override
5
+
6
+ import packaging._parser
7
+ import packaging.requirements
8
+ from packaging.requirements import _parse_requirement
9
+ from packaging.specifiers import Specifier, SpecifierSet
10
+
11
+ from utilities.iterables import OneEmptyError, one
12
+
13
+ if TYPE_CHECKING:
14
+ from packaging._parser import MarkerList
15
+
16
+
17
+ @dataclass(order=True, unsafe_hash=True, slots=True)
18
+ class Requirement:
19
+ requirement: str
20
+ _parsed_req: packaging._parser.ParsedRequirement = field(init=False, repr=False)
21
+ _custom_req: _CustomRequirement = field(init=False, repr=False)
22
+
23
+ def __getitem__(self, operator: str, /) -> str:
24
+ return self.specifier_set[operator]
25
+
26
+ def __post_init__(self) -> None:
27
+ self._parsed_req = _parse_requirement(self.requirement)
28
+ self._custom_req = _CustomRequirement(self.requirement)
29
+
30
+ @override
31
+ def __str__(self) -> str:
32
+ return str(self._custom_req)
33
+
34
+ @property
35
+ def extras(self) -> list[str]:
36
+ return self._parsed_req.extras
37
+
38
+ @overload
39
+ def get(self, operator: str, default: str, /) -> str: ...
40
+ @overload
41
+ def get(self, operator: str, default: None = None, /) -> str | None: ...
42
+ def get(self, operator: str, default: str | None = None, /) -> str | None:
43
+ return self.specifier_set.get(operator, default)
44
+
45
+ @property
46
+ def marker(self) -> MarkerList | None:
47
+ return self._parsed_req.marker
48
+
49
+ @property
50
+ def name(self) -> str:
51
+ return self._parsed_req.name
52
+
53
+ def replace(self, operator: str, version: str, /) -> Self:
54
+ return type(self)(str(self._custom_req.replace(operator, version)))
55
+
56
+ @property
57
+ def specifier(self) -> str:
58
+ return self._parsed_req.specifier
59
+
60
+ @property
61
+ def specifier_set(self) -> _CustomSpecifierSet:
62
+ return _CustomSpecifierSet(_parse_requirement(self.requirement).specifier)
63
+
64
+ @property
65
+ def url(self) -> str:
66
+ return self._parsed_req.url
67
+
68
+
69
+ class _CustomRequirement(packaging.requirements.Requirement):
70
+ specifier: _CustomSpecifierSet
71
+
72
+ @override
73
+ def __init__(self, requirement_string: str) -> None:
74
+ super().__init__(requirement_string)
75
+ parsed = _parse_requirement(requirement_string)
76
+ self.specifier = _CustomSpecifierSet(parsed.specifier) # pyright: ignore[reportIncompatibleVariableOverride]
77
+
78
+ def replace(self, operator: str, version: str, /) -> Self:
79
+ new = type(self)(super().__str__())
80
+ new.specifier = self.specifier.replace(operator, version)
81
+ return new
82
+
83
+
84
+ class _CustomSpecifierSet(SpecifierSet):
85
+ def __getitem__(self, operator: str, /) -> str:
86
+ try:
87
+ return one(s.version for s in self if s.operator == operator)
88
+ except OneEmptyError:
89
+ raise KeyError(operator) from None
90
+
91
+ @override
92
+ def __str__(self) -> str:
93
+ specs = sorted(self._specs, key=self._sort_key)
94
+ return ", ".join(map(str, specs))
95
+
96
+ @overload
97
+ def get(self, operator: str, default: str, /) -> str: ...
98
+ @overload
99
+ def get(self, operator: str, default: None = None, /) -> str | None: ...
100
+ def get(self, operator: str, default: str | None = None, /) -> str | None:
101
+ try:
102
+ return self[operator]
103
+ except KeyError:
104
+ return default
105
+
106
+ def replace(self, operator: str, version: str, /) -> Self:
107
+ new = Specifier(spec=f"{operator}{version}")
108
+ remainder = (s for s in self if s.operator != operator)
109
+ return type(self)([new, *remainder])
110
+
111
+ def _sort_key(self, spec: Specifier, /) -> int:
112
+ return [">=", "<"].index(spec.operator)
113
+
114
+
115
+ __all__ = ["Requirement"]
utilities/parse.py CHANGED
@@ -13,9 +13,11 @@ from whenever import (
13
13
  Date,
14
14
  DateDelta,
15
15
  DateTimeDelta,
16
+ MonthDay,
16
17
  PlainDateTime,
17
18
  Time,
18
19
  TimeDelta,
20
+ YearMonth,
19
21
  ZonedDateTime,
20
22
  )
21
23
 
@@ -51,7 +53,6 @@ from utilities.typing import (
51
53
  is_union_type,
52
54
  )
53
55
  from utilities.version import ParseVersionError, Version, parse_version
54
- from utilities.whenever import Month
55
56
 
56
57
  if TYPE_CHECKING:
57
58
  from collections.abc import Iterable, Mapping, Sequence
@@ -194,15 +195,16 @@ def _parse_object_type(
194
195
  Date,
195
196
  DateDelta,
196
197
  DateTimeDelta,
197
- Month,
198
+ MonthDay,
198
199
  PlainDateTime,
199
200
  Time,
200
201
  TimeDelta,
202
+ YearMonth,
201
203
  ZonedDateTime,
202
204
  ),
203
205
  ):
204
206
  try:
205
- return cls.parse_common_iso(text)
207
+ return cls.parse_iso(text)
206
208
  except ValueError:
207
209
  raise _ParseObjectParseError(type_=cls, text=text) from None
208
210
  if issubclass(cls, Path):
@@ -467,14 +469,15 @@ def serialize_object(
467
469
  Date,
468
470
  DateDelta,
469
471
  DateTimeDelta,
470
- Month,
472
+ MonthDay,
471
473
  PlainDateTime,
472
474
  Time,
473
475
  TimeDelta,
476
+ YearMonth,
474
477
  ZonedDateTime,
475
478
  ),
476
479
  ):
477
- return obj.format_common_iso()
480
+ return obj.format_iso()
478
481
  if isinstance(obj, Enum):
479
482
  return obj.name
480
483
  if isinstance(obj, dict):
utilities/pathlib.py CHANGED
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections.abc import Callable
4
- from contextlib import contextmanager, suppress
5
4
  from dataclasses import dataclass
6
5
  from itertools import chain
7
6
  from os import chdir
@@ -9,9 +8,13 @@ from os.path import expandvars
9
8
  from pathlib import Path
10
9
  from re import IGNORECASE, search
11
10
  from subprocess import PIPE, CalledProcessError, check_output
12
- from typing import TYPE_CHECKING, assert_never, overload, override
11
+ from typing import TYPE_CHECKING, Literal, assert_never, overload, override
13
12
 
14
- from utilities.sentinel import Sentinel, sentinel
13
+ from utilities.contextlib import enhanced_context_manager
14
+ from utilities.errors import ImpossibleCaseError
15
+ from utilities.grp import get_gid_name
16
+ from utilities.pwd import get_uid_name
17
+ from utilities.sentinel import Sentinel
15
18
 
16
19
  if TYPE_CHECKING:
17
20
  from collections.abc import Iterator, Sequence
@@ -26,10 +29,15 @@ def ensure_suffix(path: PathLike, suffix: str, /) -> Path:
26
29
  """Ensure a path has a given suffix."""
27
30
  path = Path(path)
28
31
  parts = path.name.split(".")
29
- parts = list(chain([parts[0]], (f".{p}" for p in parts[1:])))
30
- if (len(parts) == 0) or (parts[-1] != suffix):
31
- parts.append(suffix)
32
- name = "".join(parts)
32
+ suffixes = suffix.strip(".").split(".")
33
+ max_len = max(len(parts), len(suffixes))
34
+ try:
35
+ i = next(i for i in range(max_len, 0, -1) if parts[-i:] == suffixes[:i])
36
+ except StopIteration:
37
+ add = suffixes
38
+ else:
39
+ add = suffixes[i:]
40
+ name = ".".join(chain(parts, add))
33
41
  return path.with_name(name)
34
42
 
35
43
 
@@ -46,50 +54,124 @@ def expand_path(path: PathLike, /) -> Path:
46
54
  ##
47
55
 
48
56
 
49
- @overload
50
- def get_path(*, path: MaybeCallablePathLike | None) -> Path: ...
51
- @overload
52
- def get_path(*, path: Sentinel) -> Sentinel: ...
53
- def get_path(
54
- *, path: MaybeCallablePathLike | None | Sentinel = sentinel
55
- ) -> Path | None | Sentinel:
56
- """Get the path."""
57
- match path:
58
- case Path() | Sentinel():
59
- return path
60
- case str():
61
- return Path(path)
62
- case None:
63
- return Path.cwd()
64
- case Callable() as func:
65
- return get_path(path=func())
66
- case _ as never:
67
- assert_never(never)
57
+ def get_file_group(path: PathLike, /) -> str | None:
58
+ """Get the group of a file."""
59
+ return get_gid_name(to_path(path).stat().st_gid)
60
+
61
+
62
+ def get_file_owner(path: PathLike, /) -> str | None:
63
+ """Get the owner of a file."""
64
+ return get_uid_name(to_path(path).stat().st_uid)
68
65
 
69
66
 
70
67
  ##
71
68
 
72
69
 
73
- def get_root(*, path: MaybeCallablePathLike | None = None) -> Path:
74
- """Get the root of a path."""
75
- path = get_path(path=path)
70
+ def get_package_root(path: MaybeCallablePathLike = Path.cwd, /) -> Path:
71
+ """Get the package root."""
72
+ path = to_path(path)
73
+ path_dir = path.parent if path.is_file() else path
74
+ all_paths = list(chain([path_dir], path_dir.parents))
75
+ try:
76
+ return next(
77
+ p.resolve()
78
+ for p in all_paths
79
+ if any(p_i.name == "pyproject.toml" for p_i in p.iterdir())
80
+ )
81
+ except StopIteration:
82
+ raise GetPackageRootError(path=path) from None
83
+
84
+
85
+ @dataclass(kw_only=True, slots=True)
86
+ class GetPackageRootError(Exception):
87
+ path: PathLike
88
+
89
+ @override
90
+ def __str__(self) -> str:
91
+ return f"Path is not part of a package: {self.path}"
92
+
93
+
94
+ ##
95
+
96
+
97
+ def get_repo_root(path: MaybeCallablePathLike = Path.cwd, /) -> Path:
98
+ """Get the repo root."""
99
+ path = to_path(path)
100
+ path_dir = path.parent if path.is_file() else path
76
101
  try:
77
102
  output = check_output(
78
- ["git", "rev-parse", "--show-toplevel"], stderr=PIPE, cwd=path, text=True
103
+ ["git", "rev-parse", "--show-toplevel"],
104
+ stderr=PIPE,
105
+ cwd=path_dir,
106
+ text=True,
79
107
  )
80
108
  except CalledProcessError as error:
81
109
  # newer versions of git report "Not a git repository", whilst older
82
110
  # versions report "not a git repository"
83
- if not search("fatal: not a git repository", error.stderr, flags=IGNORECASE):
84
- raise # pragma: no cover
111
+ if search("fatal: not a git repository", error.stderr, flags=IGNORECASE):
112
+ raise _GetRepoRootNotARepoError(path=path) from None
113
+ raise # pragma: no cover
114
+ except FileNotFoundError as error: # pragma: no cover
115
+ if search("No such file or directory: 'git'", str(error), flags=IGNORECASE):
116
+ raise _GetRepoRootGitNotFoundError from None
117
+ raise
85
118
  else:
86
119
  return Path(output.strip("\n"))
87
- all_paths = list(chain([path], path.parents))
88
- with suppress(StopIteration):
89
- return next(
90
- p for p in all_paths if any(p_i.name == ".envrc" for p_i in p.iterdir())
91
- )
92
- raise GetRootError(path=path)
120
+
121
+
122
+ @dataclass(kw_only=True, slots=True)
123
+ class GetRepoRootError(Exception): ...
124
+
125
+
126
+ @dataclass(kw_only=True, slots=True)
127
+ class _GetRepoRootGitNotFoundError(GetRepoRootError):
128
+ @override
129
+ def __str__(self) -> str:
130
+ return "'git' not found" # pragma: no cover
131
+
132
+
133
+ @dataclass(kw_only=True, slots=True)
134
+ class _GetRepoRootNotARepoError(GetRepoRootError):
135
+ path: Path
136
+
137
+ @override
138
+ def __str__(self) -> str:
139
+ return f"Path is not part of a `git` repository: {self.path}"
140
+
141
+
142
+ ##
143
+
144
+
145
+ def get_root(path: MaybeCallablePathLike = Path.cwd, /) -> Path:
146
+ """Get the root of a path."""
147
+ path = to_path(path)
148
+ try:
149
+ repo = get_repo_root(path)
150
+ except GetRepoRootError:
151
+ repo = None
152
+ try:
153
+ package = get_package_root(path)
154
+ except GetPackageRootError:
155
+ package = None
156
+ match repo, package:
157
+ case None, None:
158
+ raise GetRootError(path=path)
159
+ case Path(), None:
160
+ return repo
161
+ case None, Path():
162
+ return package
163
+ case Path(), Path():
164
+ if repo == package:
165
+ return repo
166
+ if is_sub_path(repo, package, strict=True):
167
+ return repo
168
+ if is_sub_path(package, repo, strict=True):
169
+ return package
170
+ raise ImpossibleCaseError( # pragma: no cover
171
+ case=[f"{repo=}", f"{package=}"]
172
+ )
173
+ case never:
174
+ assert_never(never)
93
175
 
94
176
 
95
177
  @dataclass(kw_only=True, slots=True)
@@ -104,6 +186,112 @@ class GetRootError(Exception):
104
186
  ##
105
187
 
106
188
 
189
+ type _GetTailDisambiguate = Literal["raise", "earlier", "later"]
190
+
191
+
192
+ def get_tail(
193
+ path: PathLike, root: PathLike, /, *, disambiguate: _GetTailDisambiguate = "raise"
194
+ ) -> Path:
195
+ """Get the tail of a path following a root match."""
196
+ path_parts, root_parts = [Path(p).parts for p in [path, root]]
197
+ len_path, len_root = map(len, [path_parts, root_parts])
198
+ if len_root > len_path:
199
+ raise _GetTailLengthError(path=path, root=root, len_root=len_root)
200
+ candidates = {
201
+ i + len_root: path_parts[i : i + len_root]
202
+ for i in range(len_path + 1 - len_root)
203
+ }
204
+ matches = {k: v for k, v in candidates.items() if v == root_parts}
205
+ match len(matches), disambiguate:
206
+ case 0, _:
207
+ raise _GetTailEmptyError(path=path, root=root)
208
+ case 1, _:
209
+ return _get_tail_core(path, next(iter(matches)))
210
+ case _, "raise":
211
+ first, second, *_ = matches
212
+ raise _GetTailNonUniqueError(
213
+ path=path,
214
+ root=root,
215
+ first=_get_tail_core(path, first),
216
+ second=_get_tail_core(path, second),
217
+ )
218
+ case _, "earlier":
219
+ return _get_tail_core(path, next(iter(matches)))
220
+ case _, "later":
221
+ return _get_tail_core(path, next(iter(reversed(matches))))
222
+ case never:
223
+ assert_never(never)
224
+
225
+
226
+ def _get_tail_core(path: PathLike, i: int, /) -> Path:
227
+ parts = Path(path).parts
228
+ return Path(*parts[i:])
229
+
230
+
231
+ @dataclass(kw_only=True, slots=True)
232
+ class GetTailError(Exception):
233
+ path: PathLike
234
+ root: PathLike
235
+
236
+
237
+ @dataclass(kw_only=True, slots=True)
238
+ class _GetTailLengthError(GetTailError):
239
+ len_root: int
240
+
241
+ @override
242
+ def __str__(self) -> str:
243
+ return f"Unable to get the tail of {str(self.path)!r} with root of length {self.len_root}"
244
+
245
+
246
+ @dataclass(kw_only=True, slots=True)
247
+ class _GetTailEmptyError(GetTailError):
248
+ @override
249
+ def __str__(self) -> str:
250
+ return (
251
+ f"Unable to get the tail of {str(self.path)!r} with root {str(self.root)!r}"
252
+ )
253
+
254
+
255
+ @dataclass(kw_only=True, slots=True)
256
+ class _GetTailNonUniqueError(GetTailError):
257
+ first: Path
258
+ second: Path
259
+
260
+ @override
261
+ def __str__(self) -> str:
262
+ return f"Path {str(self.path)!r} must contain exactly one tail with root {str(self.root)!r}; got {str(self.first)!r}, {str(self.second)!r} and perhaps more"
263
+
264
+
265
+ ##
266
+
267
+
268
+ def module_path(
269
+ path: PathLike,
270
+ /,
271
+ *,
272
+ root: PathLike | None = None,
273
+ disambiguate: _GetTailDisambiguate = "raise",
274
+ ) -> str:
275
+ """Return a module path."""
276
+ path = Path(path)
277
+ if root is not None:
278
+ path = get_tail(path, root, disambiguate=disambiguate)
279
+ parts = path.with_suffix("").parts
280
+ return ".".join(parts)
281
+
282
+
283
+ ##
284
+
285
+
286
+ def is_sub_path(x: PathLike, y: PathLike, /, *, strict: bool = False) -> bool:
287
+ """Check if a path is a sub path of another."""
288
+ x, y = [Path(i).resolve() for i in [x, y]]
289
+ return x.is_relative_to(y) and not (strict and y.is_relative_to(x))
290
+
291
+
292
+ ##
293
+
294
+
107
295
  def list_dir(path: PathLike, /) -> Sequence[Path]:
108
296
  """List the contents of a directory."""
109
297
  return sorted(Path(path).iterdir())
@@ -112,7 +300,7 @@ def list_dir(path: PathLike, /) -> Sequence[Path]:
112
300
  ##
113
301
 
114
302
 
115
- @contextmanager
303
+ @enhanced_context_manager
116
304
  def temp_cwd(path: PathLike, /) -> Iterator[None]:
117
305
  """Context manager with temporary current working directory set."""
118
306
  prev = Path.cwd()
@@ -123,4 +311,45 @@ def temp_cwd(path: PathLike, /) -> Iterator[None]:
123
311
  chdir(prev)
124
312
 
125
313
 
126
- __all__ = ["PWD", "ensure_suffix", "expand_path", "get_path", "list_dir", "temp_cwd"]
314
+ ##
315
+
316
+
317
+ @overload
318
+ def to_path(path: Sentinel, /) -> Sentinel: ...
319
+ @overload
320
+ def to_path(path: MaybeCallablePathLike | None = Path.cwd, /) -> Path: ...
321
+ def to_path(
322
+ path: MaybeCallablePathLike | None | Sentinel = Path.cwd, /
323
+ ) -> Path | Sentinel:
324
+ """Get the path."""
325
+ match path:
326
+ case Path() | Sentinel():
327
+ return path
328
+ case None:
329
+ return Path.cwd()
330
+ case str():
331
+ return Path(path)
332
+ case Callable() as func:
333
+ return to_path(func())
334
+ case never:
335
+ assert_never(never)
336
+
337
+
338
+ __all__ = [
339
+ "PWD",
340
+ "GetPackageRootError",
341
+ "GetRepoRootError",
342
+ "GetTailError",
343
+ "ensure_suffix",
344
+ "expand_path",
345
+ "get_file_group",
346
+ "get_file_owner",
347
+ "get_package_root",
348
+ "get_repo_root",
349
+ "get_tail",
350
+ "is_sub_path",
351
+ "list_dir",
352
+ "module_path",
353
+ "temp_cwd",
354
+ "to_path",
355
+ ]