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.
Files changed (96) hide show
  1. dycw_utilities-0.185.8.dist-info/METADATA +33 -0
  2. dycw_utilities-0.185.8.dist-info/RECORD +90 -0
  3. {dycw_utilities-0.166.30.dist-info → dycw_utilities-0.185.8.dist-info}/WHEEL +1 -1
  4. {dycw_utilities-0.166.30.dist-info → dycw_utilities-0.185.8.dist-info}/entry_points.txt +1 -0
  5. utilities/__init__.py +1 -1
  6. utilities/altair.py +17 -10
  7. utilities/asyncio.py +50 -72
  8. utilities/atools.py +9 -11
  9. utilities/cachetools.py +16 -11
  10. utilities/click.py +76 -19
  11. utilities/concurrent.py +1 -1
  12. utilities/constants.py +492 -0
  13. utilities/contextlib.py +23 -30
  14. utilities/contextvars.py +1 -23
  15. utilities/core.py +2581 -0
  16. utilities/dataclasses.py +16 -119
  17. utilities/docker.py +387 -0
  18. utilities/enum.py +1 -1
  19. utilities/errors.py +2 -16
  20. utilities/fastapi.py +5 -5
  21. utilities/fpdf2.py +2 -1
  22. utilities/functions.py +34 -265
  23. utilities/http.py +2 -3
  24. utilities/hypothesis.py +84 -29
  25. utilities/importlib.py +17 -1
  26. utilities/iterables.py +39 -575
  27. utilities/jinja2.py +145 -0
  28. utilities/jupyter.py +5 -3
  29. utilities/libcst.py +1 -1
  30. utilities/lightweight_charts.py +4 -6
  31. utilities/logging.py +24 -24
  32. utilities/math.py +1 -36
  33. utilities/more_itertools.py +4 -6
  34. utilities/numpy.py +2 -1
  35. utilities/operator.py +2 -2
  36. utilities/orjson.py +42 -43
  37. utilities/os.py +4 -147
  38. utilities/packaging.py +129 -0
  39. utilities/parse.py +35 -15
  40. utilities/pathlib.py +3 -120
  41. utilities/platform.py +8 -90
  42. utilities/polars.py +38 -32
  43. utilities/postgres.py +37 -33
  44. utilities/pottery.py +20 -18
  45. utilities/pqdm.py +3 -4
  46. utilities/psutil.py +2 -3
  47. utilities/pydantic.py +25 -0
  48. utilities/pydantic_settings.py +87 -16
  49. utilities/pydantic_settings_sops.py +16 -3
  50. utilities/pyinstrument.py +4 -4
  51. utilities/pytest.py +96 -125
  52. utilities/pytest_plugins/pytest_regressions.py +2 -2
  53. utilities/pytest_regressions.py +32 -11
  54. utilities/random.py +2 -8
  55. utilities/redis.py +98 -94
  56. utilities/reprlib.py +11 -118
  57. utilities/shellingham.py +66 -0
  58. utilities/shutil.py +25 -0
  59. utilities/slack_sdk.py +13 -12
  60. utilities/sqlalchemy.py +57 -30
  61. utilities/sqlalchemy_polars.py +16 -25
  62. utilities/subprocess.py +2590 -0
  63. utilities/tabulate.py +32 -0
  64. utilities/testbook.py +8 -8
  65. utilities/text.py +24 -99
  66. utilities/throttle.py +159 -0
  67. utilities/time.py +18 -0
  68. utilities/timer.py +31 -14
  69. utilities/traceback.py +16 -23
  70. utilities/types.py +42 -2
  71. utilities/typing.py +26 -14
  72. utilities/uuid.py +1 -1
  73. utilities/version.py +202 -45
  74. utilities/whenever.py +53 -150
  75. dycw_utilities-0.166.30.dist-info/METADATA +0 -41
  76. dycw_utilities-0.166.30.dist-info/RECORD +0 -98
  77. dycw_utilities-0.166.30.dist-info/licenses/LICENSE +0 -21
  78. utilities/aeventkit.py +0 -388
  79. utilities/atomicwrites.py +0 -182
  80. utilities/cryptography.py +0 -41
  81. utilities/getpass.py +0 -8
  82. utilities/git.py +0 -19
  83. utilities/gzip.py +0 -31
  84. utilities/json.py +0 -70
  85. utilities/pickle.py +0 -25
  86. utilities/re.py +0 -156
  87. utilities/sentinel.py +0 -73
  88. utilities/socket.py +0 -8
  89. utilities/string.py +0 -20
  90. utilities/tempfile.py +0 -77
  91. utilities/typed_settings.py +0 -152
  92. utilities/tzdata.py +0 -11
  93. utilities/tzlocal.py +0 -28
  94. utilities/warnings.py +0 -65
  95. utilities/zipfile.py +0 -25
  96. 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, get_class_name
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 _HelpOptionNames(TypedDict):
41
- help_option_names: list[str]
42
+ class _ContextSettings(TypedDict):
43
+ context_settings: _ContextSettingsInner
42
44
 
43
45
 
44
- class _ContextSettings(TypedDict):
45
- context_settings: _HelpOptionNames
46
+ class _ContextSettingsInner(TypedDict):
47
+ max_content_width: int
48
+ help_option_names: list[str]
49
+ show_default: bool
46
50
 
47
51
 
48
- CONTEXT_SETTINGS_HELP_OPTION_NAMES = _ContextSettings(
49
- context_settings=_HelpOptionNames(help_option_names=["-h", "--help"])
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.parse_common_iso(value)
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.parse_common_iso(value)
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.parse_common_iso(value)
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__(self, enum: type[E], /, *, case_sensitive: bool = False) -> None:
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.parse_common_iso(value)
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.parse_common_iso(value)
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.parse_common_iso(value)
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.parse_common_iso(value)
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.parse_common_iso(value)
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.parse_common_iso(value)
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
- "CONTEXT_SETTINGS_HELP_OPTION_NAMES",
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
@@ -12,7 +12,7 @@ if TYPE_CHECKING:
12
12
  from collections.abc import Callable, Iterable
13
13
  from multiprocessing.context import BaseContext
14
14
 
15
- from utilities.os import IntOrAll
15
+ from utilities.types import IntOrAll
16
16
 
17
17
 
18
18
  def concurrent_map[T](
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
+ ]