dycw-utilities 0.166.30__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.166.30.dist-info → dycw_utilities-0.185.8.dist-info}/WHEEL +1 -1
- {dycw_utilities-0.166.30.dist-info → dycw_utilities-0.185.8.dist-info}/entry_points.txt +1 -0
- utilities/__init__.py +1 -1
- utilities/altair.py +17 -10
- utilities/asyncio.py +50 -72
- utilities/atools.py +9 -11
- utilities/cachetools.py +16 -11
- utilities/click.py +76 -19
- 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 +387 -0
- utilities/enum.py +1 -1
- utilities/errors.py +2 -16
- utilities/fastapi.py +5 -5
- utilities/fpdf2.py +2 -1
- utilities/functions.py +34 -265
- utilities/http.py +2 -3
- utilities/hypothesis.py +84 -29
- utilities/importlib.py +17 -1
- utilities/iterables.py +39 -575
- utilities/jinja2.py +145 -0
- utilities/jupyter.py +5 -3
- utilities/libcst.py +1 -1
- utilities/lightweight_charts.py +4 -6
- utilities/logging.py +24 -24
- utilities/math.py +1 -36
- utilities/more_itertools.py +4 -6
- utilities/numpy.py +2 -1
- utilities/operator.py +2 -2
- utilities/orjson.py +42 -43
- utilities/os.py +4 -147
- utilities/packaging.py +129 -0
- utilities/parse.py +35 -15
- utilities/pathlib.py +3 -120
- utilities/platform.py +8 -90
- utilities/polars.py +38 -32
- utilities/postgres.py +37 -33
- utilities/pottery.py +20 -18
- utilities/pqdm.py +3 -4
- utilities/psutil.py +2 -3
- utilities/pydantic.py +25 -0
- utilities/pydantic_settings.py +87 -16
- utilities/pydantic_settings_sops.py +16 -3
- utilities/pyinstrument.py +4 -4
- utilities/pytest.py +96 -125
- utilities/pytest_plugins/pytest_regressions.py +2 -2
- utilities/pytest_regressions.py +32 -11
- utilities/random.py +2 -8
- utilities/redis.py +98 -94
- utilities/reprlib.py +11 -118
- utilities/shellingham.py +66 -0
- utilities/shutil.py +25 -0
- utilities/slack_sdk.py +13 -12
- utilities/sqlalchemy.py +57 -30
- utilities/sqlalchemy_polars.py +16 -25
- utilities/subprocess.py +2590 -0
- utilities/tabulate.py +32 -0
- utilities/testbook.py +8 -8
- utilities/text.py +24 -99
- utilities/throttle.py +159 -0
- utilities/time.py +18 -0
- utilities/timer.py +31 -14
- utilities/traceback.py +16 -23
- utilities/types.py +42 -2
- utilities/typing.py +26 -14
- utilities/uuid.py +1 -1
- utilities/version.py +202 -45
- utilities/whenever.py +53 -150
- dycw_utilities-0.166.30.dist-info/METADATA +0 -41
- dycw_utilities-0.166.30.dist-info/RECORD +0 -98
- dycw_utilities-0.166.30.dist-info/licenses/LICENSE +0 -21
- utilities/aeventkit.py +0 -388
- utilities/atomicwrites.py +0 -182
- utilities/cryptography.py +0 -41
- utilities/getpass.py +0 -8
- utilities/git.py +0 -19
- utilities/gzip.py +0 -31
- utilities/json.py +0 -70
- utilities/pickle.py +0 -25
- utilities/re.py +0 -156
- utilities/sentinel.py +0 -73
- utilities/socket.py +0 -8
- utilities/string.py +0 -20
- utilities/tempfile.py +0 -77
- utilities/typed_settings.py +0 -152
- 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
utilities/click.py
CHANGED
|
@@ -4,14 +4,16 @@ import enum
|
|
|
4
4
|
import ipaddress
|
|
5
5
|
import pathlib
|
|
6
6
|
import uuid
|
|
7
|
+
from enum import StrEnum
|
|
7
8
|
from typing import TYPE_CHECKING, TypedDict, assert_never, override
|
|
8
9
|
|
|
9
10
|
import whenever
|
|
10
11
|
from click import Choice, Context, Parameter, ParamType
|
|
11
12
|
from click.types import IntParamType, StringParamType
|
|
12
13
|
|
|
14
|
+
from utilities.core import get_class, get_class_name, one
|
|
13
15
|
from utilities.enum import EnsureEnumError, ensure_enum
|
|
14
|
-
from utilities.functions import EnsureStrError, ensure_str
|
|
16
|
+
from utilities.functions import EnsureStrError, ensure_str
|
|
15
17
|
from utilities.iterables import is_iterable_not_str
|
|
16
18
|
from utilities.parse import ParseObjectError, parse_object
|
|
17
19
|
from utilities.text import split_str
|
|
@@ -37,17 +39,23 @@ if TYPE_CHECKING:
|
|
|
37
39
|
)
|
|
38
40
|
|
|
39
41
|
|
|
40
|
-
class
|
|
41
|
-
|
|
42
|
+
class _ContextSettings(TypedDict):
|
|
43
|
+
context_settings: _ContextSettingsInner
|
|
42
44
|
|
|
43
45
|
|
|
44
|
-
class
|
|
45
|
-
|
|
46
|
+
class _ContextSettingsInner(TypedDict):
|
|
47
|
+
max_content_width: int
|
|
48
|
+
help_option_names: list[str]
|
|
49
|
+
show_default: bool
|
|
46
50
|
|
|
47
51
|
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
_MAX_CONTENT_WIDTH = 120
|
|
53
|
+
_CONTEXT_SETTINGS_INNER = _ContextSettingsInner(
|
|
54
|
+
max_content_width=_MAX_CONTENT_WIDTH,
|
|
55
|
+
help_option_names=["-h", "--help"],
|
|
56
|
+
show_default=True,
|
|
50
57
|
)
|
|
58
|
+
CONTEXT_SETTINGS = _ContextSettings(context_settings=_CONTEXT_SETTINGS_INNER)
|
|
51
59
|
|
|
52
60
|
|
|
53
61
|
# parameters
|
|
@@ -72,7 +80,7 @@ class Date(ParamType):
|
|
|
72
80
|
return value
|
|
73
81
|
case str():
|
|
74
82
|
try:
|
|
75
|
-
return whenever.Date.
|
|
83
|
+
return whenever.Date.parse_iso(value)
|
|
76
84
|
except ValueError as error:
|
|
77
85
|
self.fail(str(error), param, ctx)
|
|
78
86
|
case never:
|
|
@@ -98,7 +106,7 @@ class DateDelta(ParamType):
|
|
|
98
106
|
return value
|
|
99
107
|
case str():
|
|
100
108
|
try:
|
|
101
|
-
return whenever.DateDelta.
|
|
109
|
+
return whenever.DateDelta.parse_iso(value)
|
|
102
110
|
except ValueError as error:
|
|
103
111
|
self.fail(str(error), param, ctx)
|
|
104
112
|
case never:
|
|
@@ -124,7 +132,7 @@ class DateTimeDelta(ParamType):
|
|
|
124
132
|
return value
|
|
125
133
|
case str():
|
|
126
134
|
try:
|
|
127
|
-
return whenever.DateTimeDelta.
|
|
135
|
+
return whenever.DateTimeDelta.parse_iso(value)
|
|
128
136
|
except ValueError as error:
|
|
129
137
|
self.fail(str(error), param, ctx)
|
|
130
138
|
case never:
|
|
@@ -135,10 +143,13 @@ class Enum[E: enum.Enum](ParamType):
|
|
|
135
143
|
"""An enum-valued parameter."""
|
|
136
144
|
|
|
137
145
|
@override
|
|
138
|
-
def __init__(
|
|
146
|
+
def __init__(
|
|
147
|
+
self, enum: type[E], /, *, value: bool = False, case_sensitive: bool = False
|
|
148
|
+
) -> None:
|
|
139
149
|
cls = get_class_name(enum)
|
|
140
150
|
self.name = f"enum[{cls}]"
|
|
141
151
|
self._enum = enum
|
|
152
|
+
self._value = issubclass(self._enum, StrEnum) or value
|
|
142
153
|
self._case_sensitive = case_sensitive
|
|
143
154
|
super().__init__()
|
|
144
155
|
|
|
@@ -160,7 +171,52 @@ class Enum[E: enum.Enum](ParamType):
|
|
|
160
171
|
@override
|
|
161
172
|
def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
|
|
162
173
|
_ = ctx
|
|
163
|
-
desc = ",".join(e.name for e in self._enum)
|
|
174
|
+
desc = ",".join(str(e.value) if self._value else e.name for e in self._enum)
|
|
175
|
+
return _make_metavar(param, desc)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class EnumPartial[E: enum.Enum](ParamType):
|
|
179
|
+
"""An enum-valued parameter."""
|
|
180
|
+
|
|
181
|
+
@override
|
|
182
|
+
def __init__(
|
|
183
|
+
self,
|
|
184
|
+
members: Iterable[E],
|
|
185
|
+
/,
|
|
186
|
+
*,
|
|
187
|
+
value: bool = False,
|
|
188
|
+
case_sensitive: bool = False,
|
|
189
|
+
) -> None:
|
|
190
|
+
self._members = list(members)
|
|
191
|
+
self._enum = one({get_class(e) for e in self._members})
|
|
192
|
+
cls = get_class_name(self._enum)
|
|
193
|
+
self.name = f"enum-partial[{cls}]"
|
|
194
|
+
self._value = issubclass(self._enum, StrEnum) or value
|
|
195
|
+
self._case_sensitive = case_sensitive
|
|
196
|
+
super().__init__()
|
|
197
|
+
|
|
198
|
+
@override
|
|
199
|
+
def __repr__(self) -> str:
|
|
200
|
+
cls = get_class_name(self._enum)
|
|
201
|
+
return f"ENUMPARTIAL[{cls}]"
|
|
202
|
+
|
|
203
|
+
@override
|
|
204
|
+
def convert(
|
|
205
|
+
self, value: EnumLike[E], param: Parameter | None, ctx: Context | None
|
|
206
|
+
) -> E:
|
|
207
|
+
"""Convert a value into the `Enum` type."""
|
|
208
|
+
try:
|
|
209
|
+
enum = ensure_enum(value, self._enum, case_sensitive=self._case_sensitive)
|
|
210
|
+
except EnsureEnumError as error:
|
|
211
|
+
self.fail(str(error), param, ctx)
|
|
212
|
+
if enum in self._members:
|
|
213
|
+
return enum
|
|
214
|
+
return self.fail(f"{enum.value!r} is not a selected member")
|
|
215
|
+
|
|
216
|
+
@override
|
|
217
|
+
def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
|
|
218
|
+
_ = ctx
|
|
219
|
+
desc = ",".join(str(e.value) if self._value else e.name for e in self._members)
|
|
164
220
|
return _make_metavar(param, desc)
|
|
165
221
|
|
|
166
222
|
|
|
@@ -235,7 +291,7 @@ class MonthDay(ParamType):
|
|
|
235
291
|
return value
|
|
236
292
|
case str():
|
|
237
293
|
try:
|
|
238
|
-
return whenever.MonthDay.
|
|
294
|
+
return whenever.MonthDay.parse_iso(value)
|
|
239
295
|
except ValueError as error:
|
|
240
296
|
self.fail(str(error), param, ctx)
|
|
241
297
|
case never:
|
|
@@ -284,7 +340,7 @@ class PlainDateTime(ParamType):
|
|
|
284
340
|
return value
|
|
285
341
|
case str():
|
|
286
342
|
try:
|
|
287
|
-
return whenever.PlainDateTime.
|
|
343
|
+
return whenever.PlainDateTime.parse_iso(value)
|
|
288
344
|
except ValueError as error:
|
|
289
345
|
self.fail(str(error), param, ctx)
|
|
290
346
|
case never:
|
|
@@ -310,7 +366,7 @@ class Time(ParamType):
|
|
|
310
366
|
return value
|
|
311
367
|
case str():
|
|
312
368
|
try:
|
|
313
|
-
return whenever.Time.
|
|
369
|
+
return whenever.Time.parse_iso(value)
|
|
314
370
|
except ValueError as error:
|
|
315
371
|
self.fail(str(error), param, ctx)
|
|
316
372
|
case never:
|
|
@@ -336,7 +392,7 @@ class TimeDelta(ParamType):
|
|
|
336
392
|
return value
|
|
337
393
|
case str():
|
|
338
394
|
try:
|
|
339
|
-
return whenever.TimeDelta.
|
|
395
|
+
return whenever.TimeDelta.parse_iso(value)
|
|
340
396
|
except ValueError as error:
|
|
341
397
|
self.fail(str(error), param, ctx)
|
|
342
398
|
case never:
|
|
@@ -388,7 +444,7 @@ class YearMonth(ParamType):
|
|
|
388
444
|
return value
|
|
389
445
|
case str():
|
|
390
446
|
try:
|
|
391
|
-
return whenever.YearMonth.
|
|
447
|
+
return whenever.YearMonth.parse_iso(value)
|
|
392
448
|
except ValueError as error:
|
|
393
449
|
self.fail(str(error), param, ctx)
|
|
394
450
|
case never:
|
|
@@ -414,7 +470,7 @@ class ZonedDateTime(ParamType):
|
|
|
414
470
|
return value
|
|
415
471
|
case str():
|
|
416
472
|
try:
|
|
417
|
-
return whenever.ZonedDateTime.
|
|
473
|
+
return whenever.ZonedDateTime.parse_iso(value)
|
|
418
474
|
except ValueError as error:
|
|
419
475
|
self.fail(str(error), param, ctx)
|
|
420
476
|
case never:
|
|
@@ -600,12 +656,13 @@ def _make_metavar(param: Parameter, desc: str, /) -> str:
|
|
|
600
656
|
|
|
601
657
|
|
|
602
658
|
__all__ = [
|
|
603
|
-
"
|
|
659
|
+
"CONTEXT_SETTINGS",
|
|
604
660
|
"UUID",
|
|
605
661
|
"Date",
|
|
606
662
|
"DateDelta",
|
|
607
663
|
"DateTimeDelta",
|
|
608
664
|
"Enum",
|
|
665
|
+
"EnumPartial",
|
|
609
666
|
"FrozenSetChoices",
|
|
610
667
|
"FrozenSetEnums",
|
|
611
668
|
"FrozenSetParameter",
|
utilities/concurrent.py
CHANGED
utilities/constants.py
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from getpass import getuser
|
|
6
|
+
from logging import getLogger
|
|
7
|
+
from os import cpu_count, environ
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from platform import system
|
|
10
|
+
from random import SystemRandom
|
|
11
|
+
from re import IGNORECASE
|
|
12
|
+
from socket import gethostname
|
|
13
|
+
from tempfile import gettempdir
|
|
14
|
+
from typing import TYPE_CHECKING, Any, assert_never, cast, override
|
|
15
|
+
from zoneinfo import ZoneInfo
|
|
16
|
+
|
|
17
|
+
from tzlocal import get_localzone
|
|
18
|
+
from whenever import (
|
|
19
|
+
Date,
|
|
20
|
+
DateDelta,
|
|
21
|
+
DateTimeDelta,
|
|
22
|
+
PlainDateTime,
|
|
23
|
+
Time,
|
|
24
|
+
TimeDelta,
|
|
25
|
+
ZonedDateTime,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from utilities.types import System, TimeZone
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# getpass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
USER: str = getuser()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# math
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
MIN_FLOAT32, MAX_FLOAT32 = -3.4028234663852886e38, 3.4028234663852886e38
|
|
42
|
+
MIN_FLOAT64, MAX_FLOAT64 = -1.7976931348623157e308, 1.7976931348623157e308
|
|
43
|
+
MIN_INT8, MAX_INT8 = -(2 ** (8 - 1)), 2 ** (8 - 1) - 1
|
|
44
|
+
MIN_INT16, MAX_INT16 = -(2 ** (16 - 1)), 2 ** (16 - 1) - 1
|
|
45
|
+
MIN_INT32, MAX_INT32 = -(2 ** (32 - 1)), 2 ** (32 - 1) - 1
|
|
46
|
+
MIN_INT64, MAX_INT64 = -(2 ** (64 - 1)), 2 ** (64 - 1) - 1
|
|
47
|
+
MIN_UINT8, MAX_UINT8 = 0, 2**8 - 1
|
|
48
|
+
MIN_UINT16, MAX_UINT16 = 0, 2**16 - 1
|
|
49
|
+
MIN_UINT32, MAX_UINT32 = 0, 2**32 - 1
|
|
50
|
+
MIN_UINT64, MAX_UINT64 = 0, 2**64 - 1
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# os
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
IS_CI: bool = "CI" in environ
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _get_cpu_count() -> int:
|
|
60
|
+
"""Get the CPU count."""
|
|
61
|
+
count = cpu_count()
|
|
62
|
+
if count is None: # pragma: no cover
|
|
63
|
+
raise ValueError(count)
|
|
64
|
+
return count
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
CPU_COUNT: int = _get_cpu_count()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# platform
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _get_system() -> System:
|
|
74
|
+
"""Get the system/OS name."""
|
|
75
|
+
sys = system()
|
|
76
|
+
if sys == "Windows": # skipif-not-windows
|
|
77
|
+
return "windows"
|
|
78
|
+
if sys == "Darwin": # skipif-not-macos
|
|
79
|
+
return "mac"
|
|
80
|
+
if sys == "Linux": # skipif-not-linux
|
|
81
|
+
return "linux"
|
|
82
|
+
raise ValueError(sys) # pragma: no cover
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
SYSTEM: System = _get_system()
|
|
86
|
+
IS_WINDOWS: bool = SYSTEM == "windows"
|
|
87
|
+
IS_MAC: bool = SYSTEM == "mac"
|
|
88
|
+
IS_LINUX: bool = SYSTEM == "linux"
|
|
89
|
+
IS_NOT_WINDOWS: bool = not IS_WINDOWS
|
|
90
|
+
IS_NOT_MAC: bool = not IS_MAC
|
|
91
|
+
IS_NOT_LINUX: bool = not IS_LINUX
|
|
92
|
+
IS_CI_AND_WINDOWS: bool = IS_CI and IS_WINDOWS
|
|
93
|
+
IS_CI_AND_MAC: bool = IS_CI and IS_MAC
|
|
94
|
+
IS_CI_AND_LINUX: bool = IS_CI and IS_LINUX
|
|
95
|
+
IS_CI_AND_NOT_WINDOWS: bool = IS_CI and IS_NOT_WINDOWS
|
|
96
|
+
IS_CI_AND_NOT_MAC: bool = IS_CI and IS_NOT_MAC
|
|
97
|
+
IS_CI_AND_NOT_LINUX: bool = IS_CI and IS_NOT_LINUX
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _get_max_pid() -> int | None:
|
|
101
|
+
"""Get the system max process ID."""
|
|
102
|
+
match SYSTEM:
|
|
103
|
+
case "windows": # skipif-not-windows
|
|
104
|
+
return None
|
|
105
|
+
case "mac": # skipif-not-macos
|
|
106
|
+
return 99999
|
|
107
|
+
case "linux": # skipif-not-linux
|
|
108
|
+
path = Path("/proc/sys/kernel/pid_max")
|
|
109
|
+
try:
|
|
110
|
+
return int(path.read_text())
|
|
111
|
+
except FileNotFoundError: # pragma: no cover
|
|
112
|
+
return None
|
|
113
|
+
case never:
|
|
114
|
+
assert_never(never)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
MAX_PID: int | None = _get_max_pid()
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# pathlib
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
HOME: Path = Path.home()
|
|
124
|
+
PWD: Path = Path.cwd()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# platform -> os
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _get_effective_group_id() -> int | None:
|
|
131
|
+
"""Get the effective group ID."""
|
|
132
|
+
match SYSTEM:
|
|
133
|
+
case "windows": # skipif-not-windows
|
|
134
|
+
return None
|
|
135
|
+
case "mac" | "linux": # skipif-windows
|
|
136
|
+
from os import getegid
|
|
137
|
+
|
|
138
|
+
return getegid()
|
|
139
|
+
case never:
|
|
140
|
+
assert_never(never)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
EFFECTIVE_GROUP_ID: int | None = _get_effective_group_id()
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _get_effective_user_id() -> int | None:
|
|
147
|
+
"""Get the effective user ID."""
|
|
148
|
+
match SYSTEM:
|
|
149
|
+
case "windows": # skipif-not-windows
|
|
150
|
+
return None
|
|
151
|
+
case "mac" | "linux": # skipif-windows
|
|
152
|
+
from os import geteuid
|
|
153
|
+
|
|
154
|
+
return geteuid()
|
|
155
|
+
case never:
|
|
156
|
+
assert_never(never)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
EFFECTIVE_USER_ID: int | None = _get_effective_user_id()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# platform -> os -> grp
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _get_gid_name(gid: int, /) -> str | None:
|
|
166
|
+
"""Get the name of a group ID."""
|
|
167
|
+
match SYSTEM:
|
|
168
|
+
case "windows": # skipif-not-windows
|
|
169
|
+
return None
|
|
170
|
+
case "mac" | "linux":
|
|
171
|
+
from grp import getgrgid
|
|
172
|
+
|
|
173
|
+
return getgrgid(gid).gr_name
|
|
174
|
+
case never:
|
|
175
|
+
assert_never(never)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
ROOT_GROUP_NAME: str | None = _get_gid_name(0)
|
|
179
|
+
EFFECTIVE_GROUP_NAME: str | None = (
|
|
180
|
+
None if EFFECTIVE_GROUP_ID is None else _get_gid_name(EFFECTIVE_GROUP_ID)
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# platform -> os -> pwd
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _get_uid_name(uid: int, /) -> str | None:
|
|
188
|
+
"""Get the name of a user ID."""
|
|
189
|
+
match SYSTEM:
|
|
190
|
+
case "windows": # skipif-not-windows
|
|
191
|
+
return None
|
|
192
|
+
case "mac" | "linux": # skipif-windows
|
|
193
|
+
from pwd import getpwuid
|
|
194
|
+
|
|
195
|
+
return getpwuid(uid).pw_name
|
|
196
|
+
case never:
|
|
197
|
+
assert_never(never)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
ROOT_USER_NAME: str | None = _get_uid_name(0)
|
|
201
|
+
EFFECTIVE_USER_NAME: str | None = (
|
|
202
|
+
None if EFFECTIVE_USER_ID is None else _get_uid_name(EFFECTIVE_USER_ID)
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
# random
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
SYSTEM_RANDOM: SystemRandom = SystemRandom()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# reprlib
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
RICH_MAX_WIDTH: int = 80
|
|
216
|
+
RICH_INDENT_SIZE: int = 4
|
|
217
|
+
RICH_MAX_LENGTH: int | None = 20
|
|
218
|
+
RICH_MAX_STRING: int | None = None
|
|
219
|
+
RICH_MAX_DEPTH: int | None = None
|
|
220
|
+
RICH_EXPAND_ALL: bool = False
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# sentinel
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class _Meta(type):
|
|
227
|
+
"""Metaclass for the sentinel."""
|
|
228
|
+
|
|
229
|
+
instance: Any = None
|
|
230
|
+
|
|
231
|
+
@override
|
|
232
|
+
def __call__(cls, *args: Any, **kwargs: Any) -> Any:
|
|
233
|
+
if cls.instance is None:
|
|
234
|
+
cls.instance = super().__call__(*args, **kwargs)
|
|
235
|
+
return cls.instance
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class Sentinel(metaclass=_Meta):
|
|
239
|
+
"""Base class for the sentinel object."""
|
|
240
|
+
|
|
241
|
+
@override
|
|
242
|
+
def __repr__(self) -> str:
|
|
243
|
+
return _SENTINEL_REPR
|
|
244
|
+
|
|
245
|
+
@override
|
|
246
|
+
def __str__(self) -> str:
|
|
247
|
+
return repr(self)
|
|
248
|
+
|
|
249
|
+
@classmethod
|
|
250
|
+
def parse(cls, text: str, /) -> Sentinel:
|
|
251
|
+
"""Parse a string into the Sentinel value."""
|
|
252
|
+
if _SENTINEL_PATTERN.search(text):
|
|
253
|
+
return sentinel
|
|
254
|
+
raise SentinelParseError(text=text)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
_SENTINEL_PATTERN = re.compile("^(|sentinel|<sentinel>)$", flags=IGNORECASE)
|
|
258
|
+
_SENTINEL_REPR = "<sentinel>"
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@dataclass(kw_only=True, slots=True)
|
|
262
|
+
class SentinelParseError(Exception):
|
|
263
|
+
text: str
|
|
264
|
+
|
|
265
|
+
@override
|
|
266
|
+
def __str__(self) -> str:
|
|
267
|
+
return f"Unable to parse sentinel; got {self.text!r}"
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
sentinel = Sentinel()
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
# socket
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
HOSTNAME = gethostname()
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
# tempfile
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
TEMP_DIR: Path = Path(gettempdir())
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
# text
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
LIST_SEPARATOR: str = ","
|
|
289
|
+
PAIR_SEPARATOR: str = "="
|
|
290
|
+
BRACKETS: set[tuple[str, str]] = {("(", ")"), ("[", "]"), ("{", "}")}
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
# tzlocal
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _get_local_time_zone() -> ZoneInfo:
|
|
297
|
+
"""Get the local time zone, with the logging disabled."""
|
|
298
|
+
logger = getLogger("tzlocal") # avoid import cycle
|
|
299
|
+
init_disabled = logger.disabled
|
|
300
|
+
logger.disabled = True
|
|
301
|
+
time_zone = get_localzone()
|
|
302
|
+
logger.disabled = init_disabled
|
|
303
|
+
return time_zone
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
LOCAL_TIME_ZONE: ZoneInfo = _get_local_time_zone()
|
|
307
|
+
LOCAL_TIME_ZONE_NAME: TimeZone = cast("TimeZone", LOCAL_TIME_ZONE.key)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
# tzlocal -> whenever
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _get_now_local() -> ZonedDateTime:
|
|
314
|
+
"""Get the current zoned date-time in the local time-zone."""
|
|
315
|
+
return ZonedDateTime.now(LOCAL_TIME_ZONE_NAME)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
NOW_LOCAL: ZonedDateTime = _get_now_local()
|
|
319
|
+
TODAY_LOCAL: Date = NOW_LOCAL.date()
|
|
320
|
+
TIME_LOCAL: Time = NOW_LOCAL.time()
|
|
321
|
+
NOW_LOCAL_PLAIN: PlainDateTime = NOW_LOCAL.to_plain()
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
# whenever
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
ZERO_DAYS: DateDelta = DateDelta()
|
|
328
|
+
ZERO_TIME: TimeDelta = TimeDelta()
|
|
329
|
+
NANOSECOND: TimeDelta = TimeDelta(nanoseconds=1)
|
|
330
|
+
MICROSECOND: TimeDelta = TimeDelta(microseconds=1)
|
|
331
|
+
MILLISECOND: TimeDelta = TimeDelta(milliseconds=1)
|
|
332
|
+
SECOND: TimeDelta = TimeDelta(seconds=1)
|
|
333
|
+
MINUTE: TimeDelta = TimeDelta(minutes=1)
|
|
334
|
+
HOUR: TimeDelta = TimeDelta(hours=1)
|
|
335
|
+
DAY: DateDelta = DateDelta(days=1)
|
|
336
|
+
WEEK: DateDelta = DateDelta(weeks=1)
|
|
337
|
+
MONTH: DateDelta = DateDelta(months=1)
|
|
338
|
+
YEAR: DateDelta = DateDelta(years=1)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
DATE_DELTA_MIN: DateDelta = DateDelta(weeks=-521722, days=-5)
|
|
342
|
+
DATE_DELTA_MAX: DateDelta = DateDelta(weeks=521722, days=5)
|
|
343
|
+
TIME_DELTA_MIN: TimeDelta = TimeDelta(hours=-87831216)
|
|
344
|
+
TIME_DELTA_MAX: TimeDelta = TimeDelta(hours=87831216)
|
|
345
|
+
DATE_TIME_DELTA_MIN: DateTimeDelta = DateTimeDelta(
|
|
346
|
+
weeks=-521722,
|
|
347
|
+
days=-5,
|
|
348
|
+
hours=-23,
|
|
349
|
+
minutes=-59,
|
|
350
|
+
seconds=-59,
|
|
351
|
+
milliseconds=-999,
|
|
352
|
+
microseconds=-999,
|
|
353
|
+
nanoseconds=-999,
|
|
354
|
+
)
|
|
355
|
+
DATE_TIME_DELTA_MAX: DateTimeDelta = DateTimeDelta(
|
|
356
|
+
weeks=521722,
|
|
357
|
+
days=5,
|
|
358
|
+
hours=23,
|
|
359
|
+
minutes=59,
|
|
360
|
+
seconds=59,
|
|
361
|
+
milliseconds=999,
|
|
362
|
+
microseconds=999,
|
|
363
|
+
nanoseconds=999,
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
SECONDS_PER_DAY: int = 24 * 60 * 60
|
|
368
|
+
NANOSECONDS_PER_SECOND: int = 1_000_000_000
|
|
369
|
+
NANOSECONDS_PER_DAY: int = SECONDS_PER_DAY * NANOSECONDS_PER_SECOND
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
# zoneinfo
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
UTC: ZoneInfo = ZoneInfo("UTC")
|
|
376
|
+
HongKong: ZoneInfo = ZoneInfo("Asia/Hong_Kong")
|
|
377
|
+
Tokyo: ZoneInfo = ZoneInfo("Asia/Tokyo")
|
|
378
|
+
USCentral: ZoneInfo = ZoneInfo("US/Central")
|
|
379
|
+
USEastern: ZoneInfo = ZoneInfo("US/Eastern")
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
# zoneinfo -> whenever
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
ZONED_DATE_TIME_MIN: ZonedDateTime = PlainDateTime.MIN.assume_tz(UTC.key)
|
|
386
|
+
ZONED_DATE_TIME_MAX: ZonedDateTime = PlainDateTime.MAX.assume_tz(UTC.key)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def _get_now(time_zone: str = UTC.key, /) -> ZonedDateTime:
|
|
390
|
+
"""Get the current zoned date-time."""
|
|
391
|
+
return ZonedDateTime.now(time_zone)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
NOW_UTC: ZonedDateTime = _get_now()
|
|
395
|
+
TODAY_UTC: Date = NOW_UTC.date()
|
|
396
|
+
TIME_UTC: Time = NOW_UTC.time()
|
|
397
|
+
NOW_UTC_PLAIN: PlainDateTime = NOW_UTC.to_plain()
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
__all__ = [
|
|
401
|
+
"BRACKETS",
|
|
402
|
+
"CPU_COUNT",
|
|
403
|
+
"DATE_DELTA_MAX",
|
|
404
|
+
"DATE_DELTA_MIN",
|
|
405
|
+
"DATE_TIME_DELTA_MAX",
|
|
406
|
+
"DATE_TIME_DELTA_MIN",
|
|
407
|
+
"DAY",
|
|
408
|
+
"EFFECTIVE_GROUP_ID",
|
|
409
|
+
"EFFECTIVE_GROUP_NAME",
|
|
410
|
+
"EFFECTIVE_USER_ID",
|
|
411
|
+
"EFFECTIVE_USER_NAME",
|
|
412
|
+
"HOME",
|
|
413
|
+
"HOSTNAME",
|
|
414
|
+
"HOUR",
|
|
415
|
+
"IS_CI",
|
|
416
|
+
"IS_CI_AND_LINUX",
|
|
417
|
+
"IS_CI_AND_MAC",
|
|
418
|
+
"IS_CI_AND_NOT_LINUX",
|
|
419
|
+
"IS_CI_AND_NOT_MAC",
|
|
420
|
+
"IS_CI_AND_NOT_WINDOWS",
|
|
421
|
+
"IS_CI_AND_WINDOWS",
|
|
422
|
+
"IS_LINUX",
|
|
423
|
+
"IS_MAC",
|
|
424
|
+
"IS_NOT_LINUX",
|
|
425
|
+
"IS_NOT_MAC",
|
|
426
|
+
"IS_NOT_WINDOWS",
|
|
427
|
+
"IS_WINDOWS",
|
|
428
|
+
"LIST_SEPARATOR",
|
|
429
|
+
"LOCAL_TIME_ZONE",
|
|
430
|
+
"LOCAL_TIME_ZONE_NAME",
|
|
431
|
+
"MAX_FLOAT32",
|
|
432
|
+
"MAX_FLOAT64",
|
|
433
|
+
"MAX_INT8",
|
|
434
|
+
"MAX_INT16",
|
|
435
|
+
"MAX_INT32",
|
|
436
|
+
"MAX_INT64",
|
|
437
|
+
"MAX_PID",
|
|
438
|
+
"MAX_UINT8",
|
|
439
|
+
"MAX_UINT16",
|
|
440
|
+
"MAX_UINT32",
|
|
441
|
+
"MAX_UINT64",
|
|
442
|
+
"MICROSECOND",
|
|
443
|
+
"MILLISECOND",
|
|
444
|
+
"MINUTE",
|
|
445
|
+
"MIN_FLOAT32",
|
|
446
|
+
"MIN_FLOAT64",
|
|
447
|
+
"MIN_INT8",
|
|
448
|
+
"MIN_INT16",
|
|
449
|
+
"MIN_INT32",
|
|
450
|
+
"MIN_INT64",
|
|
451
|
+
"MIN_UINT8",
|
|
452
|
+
"MIN_UINT16",
|
|
453
|
+
"MIN_UINT32",
|
|
454
|
+
"MIN_UINT64",
|
|
455
|
+
"MONTH",
|
|
456
|
+
"NANOSECOND",
|
|
457
|
+
"NANOSECONDS_PER_DAY",
|
|
458
|
+
"NANOSECONDS_PER_SECOND",
|
|
459
|
+
"NOW_LOCAL",
|
|
460
|
+
"NOW_LOCAL_PLAIN",
|
|
461
|
+
"NOW_UTC",
|
|
462
|
+
"NOW_UTC_PLAIN",
|
|
463
|
+
"PAIR_SEPARATOR",
|
|
464
|
+
"PWD",
|
|
465
|
+
"ROOT_GROUP_NAME",
|
|
466
|
+
"ROOT_USER_NAME",
|
|
467
|
+
"SECOND",
|
|
468
|
+
"SECONDS_PER_DAY",
|
|
469
|
+
"SYSTEM",
|
|
470
|
+
"SYSTEM_RANDOM",
|
|
471
|
+
"TEMP_DIR",
|
|
472
|
+
"TIME_DELTA_MAX",
|
|
473
|
+
"TIME_DELTA_MIN",
|
|
474
|
+
"TIME_LOCAL",
|
|
475
|
+
"TIME_UTC",
|
|
476
|
+
"TODAY_LOCAL",
|
|
477
|
+
"TODAY_UTC",
|
|
478
|
+
"USER",
|
|
479
|
+
"UTC",
|
|
480
|
+
"WEEK",
|
|
481
|
+
"YEAR",
|
|
482
|
+
"ZERO_DAYS",
|
|
483
|
+
"ZERO_TIME",
|
|
484
|
+
"ZONED_DATE_TIME_MAX",
|
|
485
|
+
"ZONED_DATE_TIME_MIN",
|
|
486
|
+
"HongKong",
|
|
487
|
+
"Sentinel",
|
|
488
|
+
"Tokyo",
|
|
489
|
+
"USCentral",
|
|
490
|
+
"USEastern",
|
|
491
|
+
"sentinel",
|
|
492
|
+
]
|