dycw-utilities 0.175.17__py3-none-any.whl → 0.185.8__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.
- dycw_utilities-0.185.8.dist-info/METADATA +33 -0
- dycw_utilities-0.185.8.dist-info/RECORD +90 -0
- {dycw_utilities-0.175.17.dist-info → dycw_utilities-0.185.8.dist-info}/WHEEL +2 -2
- utilities/__init__.py +1 -1
- utilities/altair.py +8 -6
- utilities/asyncio.py +40 -56
- utilities/atools.py +9 -11
- utilities/cachetools.py +8 -6
- utilities/click.py +4 -3
- utilities/concurrent.py +1 -1
- utilities/constants.py +492 -0
- utilities/contextlib.py +23 -30
- utilities/contextvars.py +1 -23
- utilities/core.py +2581 -0
- utilities/dataclasses.py +16 -119
- utilities/docker.py +139 -45
- utilities/enum.py +1 -1
- utilities/errors.py +2 -16
- utilities/fastapi.py +5 -5
- utilities/fpdf2.py +2 -1
- utilities/functions.py +33 -264
- utilities/http.py +2 -3
- utilities/hypothesis.py +48 -25
- utilities/iterables.py +39 -575
- utilities/jinja2.py +3 -6
- utilities/jupyter.py +5 -3
- utilities/libcst.py +1 -1
- utilities/lightweight_charts.py +4 -6
- utilities/logging.py +17 -15
- utilities/math.py +1 -36
- utilities/more_itertools.py +4 -6
- utilities/numpy.py +2 -1
- utilities/operator.py +2 -2
- utilities/orjson.py +24 -25
- utilities/os.py +4 -185
- utilities/packaging.py +129 -0
- utilities/parse.py +33 -13
- utilities/pathlib.py +2 -136
- utilities/platform.py +8 -90
- utilities/polars.py +34 -31
- utilities/postgres.py +9 -4
- utilities/pottery.py +20 -18
- utilities/pqdm.py +3 -4
- utilities/psutil.py +2 -3
- utilities/pydantic.py +18 -4
- utilities/pydantic_settings.py +7 -9
- utilities/pydantic_settings_sops.py +3 -3
- utilities/pyinstrument.py +4 -4
- utilities/pytest.py +49 -108
- utilities/pytest_plugins/pytest_regressions.py +2 -2
- utilities/pytest_regressions.py +8 -6
- utilities/random.py +2 -8
- utilities/redis.py +98 -94
- utilities/reprlib.py +11 -118
- utilities/shellingham.py +66 -0
- utilities/slack_sdk.py +13 -12
- utilities/sqlalchemy.py +42 -30
- utilities/sqlalchemy_polars.py +16 -25
- utilities/subprocess.py +1166 -148
- utilities/tabulate.py +32 -0
- utilities/testbook.py +8 -8
- utilities/text.py +24 -115
- utilities/throttle.py +159 -0
- utilities/time.py +18 -0
- utilities/timer.py +29 -12
- utilities/traceback.py +15 -22
- utilities/types.py +38 -3
- utilities/typing.py +18 -12
- utilities/uuid.py +1 -1
- utilities/version.py +202 -45
- utilities/whenever.py +22 -150
- dycw_utilities-0.175.17.dist-info/METADATA +0 -34
- dycw_utilities-0.175.17.dist-info/RECORD +0 -103
- utilities/atomicwrites.py +0 -182
- utilities/cryptography.py +0 -41
- utilities/getpass.py +0 -8
- utilities/git.py +0 -19
- utilities/grp.py +0 -28
- utilities/gzip.py +0 -31
- utilities/json.py +0 -70
- utilities/permissions.py +0 -298
- utilities/pickle.py +0 -25
- utilities/pwd.py +0 -28
- utilities/re.py +0 -156
- utilities/sentinel.py +0 -73
- utilities/socket.py +0 -8
- utilities/string.py +0 -20
- utilities/tempfile.py +0 -136
- utilities/tzdata.py +0 -11
- utilities/tzlocal.py +0 -28
- utilities/warnings.py +0 -65
- utilities/zipfile.py +0 -25
- utilities/zoneinfo.py +0 -133
- {dycw_utilities-0.175.17.dist-info → dycw_utilities-0.185.8.dist-info}/entry_points.txt +0 -0
utilities/packaging.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
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.core 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
|
+
def drop(self, operator: str, /) -> Self:
|
|
35
|
+
return type(self)(str(self._custom_req.drop(operator)))
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def extras(self) -> list[str]:
|
|
39
|
+
return self._parsed_req.extras
|
|
40
|
+
|
|
41
|
+
@overload
|
|
42
|
+
def get(self, operator: str, default: str, /) -> str: ...
|
|
43
|
+
@overload
|
|
44
|
+
def get(self, operator: str, default: None = None, /) -> str | None: ...
|
|
45
|
+
def get(self, operator: str, default: str | None = None, /) -> str | None:
|
|
46
|
+
return self.specifier_set.get(operator, default)
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def marker(self) -> MarkerList | None:
|
|
50
|
+
return self._parsed_req.marker
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def name(self) -> str:
|
|
54
|
+
return self._parsed_req.name
|
|
55
|
+
|
|
56
|
+
def replace(self, operator: str, version: str | None, /) -> Self:
|
|
57
|
+
return type(self)(str(self._custom_req.replace(operator, version)))
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def specifier(self) -> str:
|
|
61
|
+
return self._parsed_req.specifier
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def specifier_set(self) -> _CustomSpecifierSet:
|
|
65
|
+
return _CustomSpecifierSet(_parse_requirement(self.requirement).specifier)
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def url(self) -> str:
|
|
69
|
+
return self._parsed_req.url
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class _CustomRequirement(packaging.requirements.Requirement):
|
|
73
|
+
specifier: _CustomSpecifierSet
|
|
74
|
+
|
|
75
|
+
@override
|
|
76
|
+
def __init__(self, requirement_string: str) -> None:
|
|
77
|
+
super().__init__(requirement_string)
|
|
78
|
+
parsed = _parse_requirement(requirement_string)
|
|
79
|
+
self.specifier = _CustomSpecifierSet(parsed.specifier) # pyright: ignore[reportIncompatibleVariableOverride]
|
|
80
|
+
|
|
81
|
+
def drop(self, operator: str, /) -> Self:
|
|
82
|
+
new = type(self)(super().__str__())
|
|
83
|
+
new.specifier = self.specifier.drop(operator)
|
|
84
|
+
return new
|
|
85
|
+
|
|
86
|
+
def replace(self, operator: str, version: str | None, /) -> Self:
|
|
87
|
+
new = type(self)(super().__str__())
|
|
88
|
+
new.specifier = self.specifier.replace(operator, version)
|
|
89
|
+
return new
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class _CustomSpecifierSet(SpecifierSet):
|
|
93
|
+
def __getitem__(self, operator: str, /) -> str:
|
|
94
|
+
try:
|
|
95
|
+
return one(s.version for s in self if s.operator == operator)
|
|
96
|
+
except OneEmptyError:
|
|
97
|
+
raise KeyError(operator) from None
|
|
98
|
+
|
|
99
|
+
@override
|
|
100
|
+
def __str__(self) -> str:
|
|
101
|
+
specs = sorted(self._specs, key=self._sort_key)
|
|
102
|
+
return ", ".join(map(str, specs))
|
|
103
|
+
|
|
104
|
+
def drop(self, operator: str, /) -> Self:
|
|
105
|
+
if any(s.operator == operator for s in self):
|
|
106
|
+
return type(self)(s for s in self if s.operator != operator)
|
|
107
|
+
raise KeyError(operator)
|
|
108
|
+
|
|
109
|
+
@overload
|
|
110
|
+
def get(self, operator: str, default: str, /) -> str: ...
|
|
111
|
+
@overload
|
|
112
|
+
def get(self, operator: str, default: None = None, /) -> str | None: ...
|
|
113
|
+
def get(self, operator: str, default: str | None = None, /) -> str | None:
|
|
114
|
+
try:
|
|
115
|
+
return self[operator]
|
|
116
|
+
except KeyError:
|
|
117
|
+
return default
|
|
118
|
+
|
|
119
|
+
def replace(self, operator: str, version: str | None, /) -> Self:
|
|
120
|
+
specifiers = [s for s in self if s.operator != operator]
|
|
121
|
+
if version is not None:
|
|
122
|
+
specifiers.append(Specifier(spec=f"{operator}{version}"))
|
|
123
|
+
return type(self)(specifiers)
|
|
124
|
+
|
|
125
|
+
def _sort_key(self, spec: Specifier, /) -> int:
|
|
126
|
+
return ["==", "!=", "~=", ">", ">=", "<", "<="].index(spec.operator)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
__all__ = ["Requirement"]
|
utilities/parse.py
CHANGED
|
@@ -21,15 +21,24 @@ from whenever import (
|
|
|
21
21
|
ZonedDateTime,
|
|
22
22
|
)
|
|
23
23
|
|
|
24
|
-
from utilities.
|
|
25
|
-
from utilities.iterables import OneEmptyError, OneNonUniqueError, one, one_str
|
|
26
|
-
from utilities.math import ParseNumberError, parse_number
|
|
27
|
-
from utilities.re import ExtractGroupError, extract_group
|
|
28
|
-
from utilities.sentinel import ParseSentinelError, Sentinel, parse_sentinel
|
|
29
|
-
from utilities.text import (
|
|
24
|
+
from utilities.constants import (
|
|
30
25
|
BRACKETS,
|
|
31
26
|
LIST_SEPARATOR,
|
|
32
27
|
PAIR_SEPARATOR,
|
|
28
|
+
Sentinel,
|
|
29
|
+
SentinelParseError,
|
|
30
|
+
)
|
|
31
|
+
from utilities.core import (
|
|
32
|
+
ExtractGroupError,
|
|
33
|
+
OneEmptyError,
|
|
34
|
+
OneNonUniqueError,
|
|
35
|
+
extract_group,
|
|
36
|
+
one,
|
|
37
|
+
one_str,
|
|
38
|
+
)
|
|
39
|
+
from utilities.enum import ParseEnumError, parse_enum
|
|
40
|
+
from utilities.math import ParseNumberError, parse_number
|
|
41
|
+
from utilities.text import (
|
|
33
42
|
ParseBoolError,
|
|
34
43
|
ParseNoneError,
|
|
35
44
|
join_strs,
|
|
@@ -52,7 +61,12 @@ from utilities.typing import (
|
|
|
52
61
|
is_tuple_type,
|
|
53
62
|
is_union_type,
|
|
54
63
|
)
|
|
55
|
-
from utilities.version import
|
|
64
|
+
from utilities.version import (
|
|
65
|
+
Version2,
|
|
66
|
+
Version3,
|
|
67
|
+
_Version2ParseError,
|
|
68
|
+
_Version3ParseError,
|
|
69
|
+
)
|
|
56
70
|
|
|
57
71
|
if TYPE_CHECKING:
|
|
58
72
|
from collections.abc import Iterable, Mapping, Sequence
|
|
@@ -211,13 +225,18 @@ def _parse_object_type(
|
|
|
211
225
|
return Path(text).expanduser()
|
|
212
226
|
if issubclass(cls, Sentinel):
|
|
213
227
|
try:
|
|
214
|
-
return
|
|
215
|
-
except
|
|
228
|
+
return Sentinel.parse(text)
|
|
229
|
+
except SentinelParseError:
|
|
230
|
+
raise _ParseObjectParseError(type_=cls, text=text) from None
|
|
231
|
+
if issubclass(cls, Version2):
|
|
232
|
+
try:
|
|
233
|
+
return Version2.parse(text)
|
|
234
|
+
except _Version2ParseError:
|
|
216
235
|
raise _ParseObjectParseError(type_=cls, text=text) from None
|
|
217
|
-
if issubclass(cls,
|
|
236
|
+
if issubclass(cls, Version3):
|
|
218
237
|
try:
|
|
219
|
-
return
|
|
220
|
-
except
|
|
238
|
+
return Version3.parse(text)
|
|
239
|
+
except _Version3ParseError:
|
|
221
240
|
raise _ParseObjectParseError(type_=cls, text=text) from None
|
|
222
241
|
raise _ParseObjectParseError(type_=cls, text=text)
|
|
223
242
|
|
|
@@ -460,7 +479,8 @@ def serialize_object(
|
|
|
460
479
|
| IPv6Address
|
|
461
480
|
| Path
|
|
462
481
|
| Sentinel
|
|
463
|
-
|
|
|
482
|
+
| Version2
|
|
483
|
+
| Version3,
|
|
464
484
|
):
|
|
465
485
|
return str(obj)
|
|
466
486
|
if isinstance(
|
utilities/pathlib.py
CHANGED
|
@@ -3,28 +3,19 @@ from __future__ import annotations
|
|
|
3
3
|
from collections.abc import Callable
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from itertools import chain
|
|
6
|
-
from os import chdir
|
|
7
|
-
from os.path import expandvars
|
|
8
6
|
from pathlib import Path
|
|
9
7
|
from re import IGNORECASE, search
|
|
10
8
|
from subprocess import PIPE, CalledProcessError, check_output
|
|
11
9
|
from typing import TYPE_CHECKING, Literal, assert_never, overload, override
|
|
12
10
|
|
|
13
|
-
from utilities.
|
|
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
|
|
11
|
+
from utilities.constants import Sentinel
|
|
18
12
|
|
|
19
13
|
if TYPE_CHECKING:
|
|
20
|
-
from collections.abc import
|
|
14
|
+
from collections.abc import Sequence
|
|
21
15
|
|
|
22
16
|
from utilities.types import MaybeCallablePathLike, PathLike
|
|
23
17
|
|
|
24
18
|
|
|
25
|
-
PWD = Path.cwd()
|
|
26
|
-
|
|
27
|
-
|
|
28
19
|
def ensure_suffix(path: PathLike, suffix: str, /) -> Path:
|
|
29
20
|
"""Ensure a path has a given suffix."""
|
|
30
21
|
path = Path(path)
|
|
@@ -44,56 +35,6 @@ def ensure_suffix(path: PathLike, suffix: str, /) -> Path:
|
|
|
44
35
|
##
|
|
45
36
|
|
|
46
37
|
|
|
47
|
-
def expand_path(path: PathLike, /) -> Path:
|
|
48
|
-
"""Expand a path."""
|
|
49
|
-
path = str(path)
|
|
50
|
-
path = expandvars(path)
|
|
51
|
-
return Path(path).expanduser()
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
##
|
|
55
|
-
|
|
56
|
-
|
|
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)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
##
|
|
68
|
-
|
|
69
|
-
|
|
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
38
|
def get_repo_root(path: MaybeCallablePathLike = Path.cwd, /) -> Path:
|
|
98
39
|
"""Get the repo root."""
|
|
99
40
|
path = to_path(path)
|
|
@@ -142,50 +83,6 @@ class _GetRepoRootNotARepoError(GetRepoRootError):
|
|
|
142
83
|
##
|
|
143
84
|
|
|
144
85
|
|
|
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)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
@dataclass(kw_only=True, slots=True)
|
|
178
|
-
class GetRootError(Exception):
|
|
179
|
-
path: PathLike
|
|
180
|
-
|
|
181
|
-
@override
|
|
182
|
-
def __str__(self) -> str:
|
|
183
|
-
return f"Unable to determine root from {str(self.path)!r}"
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
##
|
|
187
|
-
|
|
188
|
-
|
|
189
86
|
type _GetTailDisambiguate = Literal["raise", "earlier", "later"]
|
|
190
87
|
|
|
191
88
|
|
|
@@ -283,15 +180,6 @@ def module_path(
|
|
|
283
180
|
##
|
|
284
181
|
|
|
285
182
|
|
|
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
|
-
|
|
295
183
|
def list_dir(path: PathLike, /) -> Sequence[Path]:
|
|
296
184
|
"""List the contents of a directory."""
|
|
297
185
|
return sorted(Path(path).iterdir())
|
|
@@ -300,20 +188,6 @@ def list_dir(path: PathLike, /) -> Sequence[Path]:
|
|
|
300
188
|
##
|
|
301
189
|
|
|
302
190
|
|
|
303
|
-
@enhanced_context_manager
|
|
304
|
-
def temp_cwd(path: PathLike, /) -> Iterator[None]:
|
|
305
|
-
"""Context manager with temporary current working directory set."""
|
|
306
|
-
prev = Path.cwd()
|
|
307
|
-
chdir(path)
|
|
308
|
-
try:
|
|
309
|
-
yield
|
|
310
|
-
finally:
|
|
311
|
-
chdir(prev)
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
##
|
|
315
|
-
|
|
316
|
-
|
|
317
191
|
@overload
|
|
318
192
|
def to_path(path: Sentinel, /) -> Sentinel: ...
|
|
319
193
|
@overload
|
|
@@ -336,20 +210,12 @@ def to_path(
|
|
|
336
210
|
|
|
337
211
|
|
|
338
212
|
__all__ = [
|
|
339
|
-
"PWD",
|
|
340
|
-
"GetPackageRootError",
|
|
341
213
|
"GetRepoRootError",
|
|
342
214
|
"GetTailError",
|
|
343
215
|
"ensure_suffix",
|
|
344
|
-
"expand_path",
|
|
345
|
-
"get_file_group",
|
|
346
|
-
"get_file_owner",
|
|
347
|
-
"get_package_root",
|
|
348
216
|
"get_repo_root",
|
|
349
217
|
"get_tail",
|
|
350
|
-
"is_sub_path",
|
|
351
218
|
"list_dir",
|
|
352
219
|
"module_path",
|
|
353
|
-
"temp_cwd",
|
|
354
220
|
"to_path",
|
|
355
221
|
]
|
utilities/platform.py
CHANGED
|
@@ -1,74 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from platform import system
|
|
6
3
|
from re import sub
|
|
7
|
-
from typing import
|
|
4
|
+
from typing import assert_never
|
|
8
5
|
|
|
9
|
-
|
|
10
|
-
from collections.abc import Iterable, Iterator
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
System = Literal["windows", "mac", "linux"]
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def get_system() -> System:
|
|
17
|
-
"""Get the system/OS name."""
|
|
18
|
-
sys = system()
|
|
19
|
-
if sys == "Windows": # skipif-not-windows
|
|
20
|
-
return "windows"
|
|
21
|
-
if sys == "Darwin": # skipif-not-macos
|
|
22
|
-
return "mac"
|
|
23
|
-
if sys == "Linux": # skipif-not-linux
|
|
24
|
-
return "linux"
|
|
25
|
-
raise GetSystemError(sys=sys) # pragma: no cover
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
@dataclass(kw_only=True, slots=True)
|
|
29
|
-
class GetSystemError(Exception):
|
|
30
|
-
sys: str
|
|
31
|
-
|
|
32
|
-
@override
|
|
33
|
-
def __str__(self) -> str:
|
|
34
|
-
return ( # pragma: no cover
|
|
35
|
-
f"System must be one of Windows, Darwin, Linux; got {self.sys!r} instead"
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
SYSTEM = get_system()
|
|
40
|
-
IS_WINDOWS = SYSTEM == "windows"
|
|
41
|
-
IS_MAC = SYSTEM == "mac"
|
|
42
|
-
IS_LINUX = SYSTEM == "linux"
|
|
43
|
-
IS_NOT_WINDOWS = not IS_WINDOWS
|
|
44
|
-
IS_NOT_MAC = not IS_MAC
|
|
45
|
-
IS_NOT_LINUX = not IS_LINUX
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
##
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def get_max_pid() -> int | None:
|
|
52
|
-
"""Get the maximum process ID."""
|
|
53
|
-
match SYSTEM:
|
|
54
|
-
case "windows": # skipif-not-windows
|
|
55
|
-
return None
|
|
56
|
-
case "mac": # skipif-not-macos
|
|
57
|
-
return 99999
|
|
58
|
-
case "linux": # skipif-not-linux
|
|
59
|
-
path = Path("/proc/sys/kernel/pid_max")
|
|
60
|
-
try:
|
|
61
|
-
return int(path.read_text())
|
|
62
|
-
except FileNotFoundError: # pragma: no cover
|
|
63
|
-
return None
|
|
64
|
-
case never:
|
|
65
|
-
assert_never(never)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
MAX_PID = get_max_pid()
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
##
|
|
6
|
+
from utilities.constants import SYSTEM
|
|
72
7
|
|
|
73
8
|
|
|
74
9
|
def get_strftime(text: str, /) -> str:
|
|
@@ -87,32 +22,15 @@ def get_strftime(text: str, /) -> str:
|
|
|
87
22
|
##
|
|
88
23
|
|
|
89
24
|
|
|
90
|
-
def
|
|
91
|
-
"""
|
|
25
|
+
def maybe_lower_case(text: str, /) -> str:
|
|
26
|
+
"""Lower-case text if the platform is case-insensitive w.r.t. filenames."""
|
|
92
27
|
match SYSTEM:
|
|
93
|
-
case "windows": # skipif-
|
|
94
|
-
|
|
95
|
-
case "mac": # skipif-not-macos
|
|
96
|
-
yield from (t.lower() for t in text)
|
|
28
|
+
case "windows" | "mac": # skipif-linux
|
|
29
|
+
return text.lower()
|
|
97
30
|
case "linux": # skipif-not-linux
|
|
98
|
-
|
|
31
|
+
return text
|
|
99
32
|
case never:
|
|
100
33
|
assert_never(never)
|
|
101
34
|
|
|
102
35
|
|
|
103
|
-
__all__ = [
|
|
104
|
-
"IS_LINUX",
|
|
105
|
-
"IS_MAC",
|
|
106
|
-
"IS_NOT_LINUX",
|
|
107
|
-
"IS_NOT_MAC",
|
|
108
|
-
"IS_NOT_WINDOWS",
|
|
109
|
-
"IS_WINDOWS",
|
|
110
|
-
"MAX_PID",
|
|
111
|
-
"SYSTEM",
|
|
112
|
-
"GetSystemError",
|
|
113
|
-
"System",
|
|
114
|
-
"get_max_pid",
|
|
115
|
-
"get_strftime",
|
|
116
|
-
"get_system",
|
|
117
|
-
"maybe_yield_lower_case",
|
|
118
|
-
]
|
|
36
|
+
__all__ = ["get_strftime", "maybe_lower_case"]
|