dycw-utilities 0.166.30__py3-none-any.whl → 0.175.17__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.175.17.dist-info/METADATA +34 -0
- {dycw_utilities-0.166.30.dist-info → dycw_utilities-0.175.17.dist-info}/RECORD +43 -38
- dycw_utilities-0.175.17.dist-info/WHEEL +4 -0
- {dycw_utilities-0.166.30.dist-info → dycw_utilities-0.175.17.dist-info}/entry_points.txt +1 -0
- utilities/__init__.py +1 -1
- utilities/altair.py +9 -4
- utilities/asyncio.py +10 -16
- utilities/cachetools.py +9 -6
- utilities/click.py +76 -20
- utilities/docker.py +293 -0
- utilities/functions.py +1 -1
- utilities/grp.py +28 -0
- utilities/hypothesis.py +38 -6
- utilities/importlib.py +17 -1
- utilities/jinja2.py +148 -0
- utilities/logging.py +7 -9
- utilities/orjson.py +18 -18
- utilities/os.py +38 -0
- utilities/parse.py +2 -2
- utilities/pathlib.py +18 -1
- utilities/permissions.py +298 -0
- utilities/platform.py +1 -1
- utilities/polars.py +4 -1
- utilities/postgres.py +28 -29
- utilities/pwd.py +28 -0
- utilities/pydantic.py +11 -0
- utilities/pydantic_settings.py +81 -8
- utilities/pydantic_settings_sops.py +13 -0
- utilities/pytest.py +60 -30
- utilities/pytest_regressions.py +26 -7
- utilities/shutil.py +25 -0
- utilities/sqlalchemy.py +15 -0
- utilities/subprocess.py +1572 -0
- utilities/tempfile.py +60 -1
- utilities/text.py +48 -32
- utilities/timer.py +2 -2
- utilities/traceback.py +1 -1
- utilities/types.py +5 -0
- utilities/typing.py +8 -2
- utilities/whenever.py +36 -5
- dycw_utilities-0.166.30.dist-info/METADATA +0 -41
- dycw_utilities-0.166.30.dist-info/WHEEL +0 -4
- dycw_utilities-0.166.30.dist-info/licenses/LICENSE +0 -21
- utilities/aeventkit.py +0 -388
- utilities/typed_settings.py +0 -152
utilities/tempfile.py
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import tempfile
|
|
4
|
+
from contextlib import contextmanager
|
|
4
5
|
from pathlib import Path
|
|
6
|
+
from shutil import move
|
|
7
|
+
from tempfile import NamedTemporaryFile as _NamedTemporaryFile
|
|
5
8
|
from tempfile import gettempdir as _gettempdir
|
|
6
9
|
from typing import TYPE_CHECKING, override
|
|
7
10
|
|
|
8
11
|
from utilities.warnings import suppress_warnings
|
|
9
12
|
|
|
10
13
|
if TYPE_CHECKING:
|
|
14
|
+
from collections.abc import Iterator
|
|
11
15
|
from types import TracebackType
|
|
12
16
|
|
|
13
17
|
from utilities.types import PathLike
|
|
@@ -66,6 +70,61 @@ class _TemporaryDirectoryNoResourceWarning(tempfile.TemporaryDirectory):
|
|
|
66
70
|
##
|
|
67
71
|
|
|
68
72
|
|
|
73
|
+
@contextmanager
|
|
74
|
+
def TemporaryFile( # noqa: N802
|
|
75
|
+
*,
|
|
76
|
+
suffix: str | None = None,
|
|
77
|
+
prefix: str | None = None,
|
|
78
|
+
dir: PathLike | None = None, # noqa: A002
|
|
79
|
+
ignore_cleanup_errors: bool = False,
|
|
80
|
+
delete: bool = True,
|
|
81
|
+
name: str | None = None,
|
|
82
|
+
text: str | None = None,
|
|
83
|
+
) -> Iterator[Path]:
|
|
84
|
+
"""Yield a temporary file."""
|
|
85
|
+
with _temporary_file_inner(
|
|
86
|
+
suffix=suffix,
|
|
87
|
+
prefix=prefix,
|
|
88
|
+
dir=dir,
|
|
89
|
+
ignore_cleanup_errors=ignore_cleanup_errors,
|
|
90
|
+
delete=delete,
|
|
91
|
+
name=name,
|
|
92
|
+
) as temp:
|
|
93
|
+
if text is not None:
|
|
94
|
+
_ = temp.write_text(text)
|
|
95
|
+
yield temp
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@contextmanager
|
|
99
|
+
def _temporary_file_inner(
|
|
100
|
+
*,
|
|
101
|
+
suffix: str | None = None,
|
|
102
|
+
prefix: str | None = None,
|
|
103
|
+
dir: PathLike | None = None, # noqa: A002
|
|
104
|
+
ignore_cleanup_errors: bool = False,
|
|
105
|
+
delete: bool = True,
|
|
106
|
+
name: str | None = None,
|
|
107
|
+
) -> Iterator[Path]:
|
|
108
|
+
with TemporaryDirectory(
|
|
109
|
+
suffix=suffix,
|
|
110
|
+
prefix=prefix,
|
|
111
|
+
dir=dir,
|
|
112
|
+
ignore_cleanup_errors=ignore_cleanup_errors,
|
|
113
|
+
delete=delete,
|
|
114
|
+
) as temp_dir:
|
|
115
|
+
temp_file = _NamedTemporaryFile( # noqa: SIM115
|
|
116
|
+
dir=temp_dir, delete=delete, delete_on_close=False
|
|
117
|
+
)
|
|
118
|
+
if name is None:
|
|
119
|
+
yield temp_dir / temp_file.name
|
|
120
|
+
else:
|
|
121
|
+
_ = move(temp_dir / temp_file.name, temp_dir / name)
|
|
122
|
+
yield temp_dir / name
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
##
|
|
126
|
+
|
|
127
|
+
|
|
69
128
|
def gettempdir() -> Path:
|
|
70
129
|
"""Get the name of the directory used for temporary files."""
|
|
71
130
|
return Path(_gettempdir())
|
|
@@ -74,4 +133,4 @@ def gettempdir() -> Path:
|
|
|
74
133
|
TEMP_DIR = gettempdir()
|
|
75
134
|
|
|
76
135
|
|
|
77
|
-
__all__ = ["TEMP_DIR", "TemporaryDirectory", "gettempdir"]
|
|
136
|
+
__all__ = ["TEMP_DIR", "TemporaryDirectory", "TemporaryFile", "gettempdir"]
|
utilities/text.py
CHANGED
|
@@ -31,7 +31,15 @@ if TYPE_CHECKING:
|
|
|
31
31
|
from utilities.types import MaybeCallableBoolLike, MaybeCallableStr, StrStrMapping
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
_DEFAULT_SEPARATOR = ","
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def kebab_case(text: str, /) -> str:
|
|
41
|
+
"""Convert text into kebab case."""
|
|
42
|
+
return _kebab_snake_case(text, "-")
|
|
35
43
|
|
|
36
44
|
|
|
37
45
|
##
|
|
@@ -110,27 +118,13 @@ def repr_encode(obj: Any, /) -> bytes:
|
|
|
110
118
|
|
|
111
119
|
def snake_case(text: str, /) -> str:
|
|
112
120
|
"""Convert text into snake case."""
|
|
113
|
-
|
|
114
|
-
trailing = bool(search(r"_$", text))
|
|
115
|
-
parts = _SPLIT_TEXT.findall(text)
|
|
116
|
-
parts = (p for p in parts if len(p) >= 1)
|
|
117
|
-
parts = chain([""] if leading else [], parts, [""] if trailing else [])
|
|
118
|
-
return "_".join(parts).lower()
|
|
121
|
+
return _kebab_snake_case(text, "_")
|
|
119
122
|
|
|
120
123
|
|
|
121
|
-
_SPLIT_TEXT = re.compile(
|
|
122
|
-
r"""
|
|
123
|
-
[A-Z]+(?=[A-Z][a-z0-9]) | # all caps followed by Upper+lower or digit (API in APIResponse2)
|
|
124
|
-
[A-Z]?[a-z]+[0-9]* | # normal words with optional trailing digits (Text123)
|
|
125
|
-
[A-Z]+[0-9]* | # consecutive caps with optional trailing digits (ID2)
|
|
126
|
-
""",
|
|
127
|
-
flags=VERBOSE,
|
|
128
|
-
)
|
|
129
|
-
|
|
130
124
|
##
|
|
131
125
|
|
|
132
126
|
|
|
133
|
-
LIST_SEPARATOR =
|
|
127
|
+
LIST_SEPARATOR = _DEFAULT_SEPARATOR
|
|
134
128
|
PAIR_SEPARATOR = "="
|
|
135
129
|
BRACKETS = [("(", ")"), ("[", "]"), ("{", "}")]
|
|
136
130
|
|
|
@@ -140,7 +134,7 @@ def split_key_value_pairs(
|
|
|
140
134
|
text: str,
|
|
141
135
|
/,
|
|
142
136
|
*,
|
|
143
|
-
list_separator: str =
|
|
137
|
+
list_separator: str = _DEFAULT_SEPARATOR,
|
|
144
138
|
pair_separator: str = PAIR_SEPARATOR,
|
|
145
139
|
brackets: Iterable[tuple[str, str]] | None = BRACKETS,
|
|
146
140
|
mapping: Literal[True],
|
|
@@ -150,7 +144,7 @@ def split_key_value_pairs(
|
|
|
150
144
|
text: str,
|
|
151
145
|
/,
|
|
152
146
|
*,
|
|
153
|
-
list_separator: str =
|
|
147
|
+
list_separator: str = _DEFAULT_SEPARATOR,
|
|
154
148
|
pair_separator: str = PAIR_SEPARATOR,
|
|
155
149
|
brackets: Iterable[tuple[str, str]] | None = BRACKETS,
|
|
156
150
|
mapping: Literal[False] = False,
|
|
@@ -160,7 +154,7 @@ def split_key_value_pairs(
|
|
|
160
154
|
text: str,
|
|
161
155
|
/,
|
|
162
156
|
*,
|
|
163
|
-
list_separator: str =
|
|
157
|
+
list_separator: str = _DEFAULT_SEPARATOR,
|
|
164
158
|
pair_separator: str = PAIR_SEPARATOR,
|
|
165
159
|
brackets: Iterable[tuple[str, str]] | None = BRACKETS,
|
|
166
160
|
mapping: bool = False,
|
|
@@ -169,7 +163,7 @@ def split_key_value_pairs(
|
|
|
169
163
|
text: str,
|
|
170
164
|
/,
|
|
171
165
|
*,
|
|
172
|
-
list_separator: str =
|
|
166
|
+
list_separator: str = _DEFAULT_SEPARATOR,
|
|
173
167
|
pair_separator: str = PAIR_SEPARATOR,
|
|
174
168
|
brackets: Iterable[tuple[str, str]] | None = BRACKETS,
|
|
175
169
|
mapping: bool = False,
|
|
@@ -228,7 +222,7 @@ def split_str(
|
|
|
228
222
|
text: str,
|
|
229
223
|
/,
|
|
230
224
|
*,
|
|
231
|
-
separator: str =
|
|
225
|
+
separator: str = _DEFAULT_SEPARATOR,
|
|
232
226
|
brackets: Iterable[tuple[str, str]] | None = None,
|
|
233
227
|
n: Literal[1],
|
|
234
228
|
) -> tuple[str]: ...
|
|
@@ -237,7 +231,7 @@ def split_str(
|
|
|
237
231
|
text: str,
|
|
238
232
|
/,
|
|
239
233
|
*,
|
|
240
|
-
separator: str =
|
|
234
|
+
separator: str = _DEFAULT_SEPARATOR,
|
|
241
235
|
brackets: Iterable[tuple[str, str]] | None = None,
|
|
242
236
|
n: Literal[2],
|
|
243
237
|
) -> tuple[str, str]: ...
|
|
@@ -246,7 +240,7 @@ def split_str(
|
|
|
246
240
|
text: str,
|
|
247
241
|
/,
|
|
248
242
|
*,
|
|
249
|
-
separator: str =
|
|
243
|
+
separator: str = _DEFAULT_SEPARATOR,
|
|
250
244
|
brackets: Iterable[tuple[str, str]] | None = None,
|
|
251
245
|
n: Literal[3],
|
|
252
246
|
) -> tuple[str, str, str]: ...
|
|
@@ -255,7 +249,7 @@ def split_str(
|
|
|
255
249
|
text: str,
|
|
256
250
|
/,
|
|
257
251
|
*,
|
|
258
|
-
separator: str =
|
|
252
|
+
separator: str = _DEFAULT_SEPARATOR,
|
|
259
253
|
brackets: Iterable[tuple[str, str]] | None = None,
|
|
260
254
|
n: Literal[4],
|
|
261
255
|
) -> tuple[str, str, str, str]: ...
|
|
@@ -264,7 +258,7 @@ def split_str(
|
|
|
264
258
|
text: str,
|
|
265
259
|
/,
|
|
266
260
|
*,
|
|
267
|
-
separator: str =
|
|
261
|
+
separator: str = _DEFAULT_SEPARATOR,
|
|
268
262
|
brackets: Iterable[tuple[str, str]] | None = None,
|
|
269
263
|
n: Literal[5],
|
|
270
264
|
) -> tuple[str, str, str, str, str]: ...
|
|
@@ -273,7 +267,7 @@ def split_str(
|
|
|
273
267
|
text: str,
|
|
274
268
|
/,
|
|
275
269
|
*,
|
|
276
|
-
separator: str =
|
|
270
|
+
separator: str = _DEFAULT_SEPARATOR,
|
|
277
271
|
brackets: Iterable[tuple[str, str]] | None = None,
|
|
278
272
|
n: int | None = None,
|
|
279
273
|
) -> tuple[str, ...]: ...
|
|
@@ -281,7 +275,7 @@ def split_str(
|
|
|
281
275
|
text: str,
|
|
282
276
|
/,
|
|
283
277
|
*,
|
|
284
|
-
separator: str =
|
|
278
|
+
separator: str = _DEFAULT_SEPARATOR,
|
|
285
279
|
brackets: Iterable[tuple[str, str]] | None = None,
|
|
286
280
|
n: int | None = None,
|
|
287
281
|
) -> tuple[str, ...]:
|
|
@@ -306,7 +300,7 @@ def _split_str_brackets(
|
|
|
306
300
|
brackets: Iterable[tuple[str, str]],
|
|
307
301
|
/,
|
|
308
302
|
*,
|
|
309
|
-
separator: str =
|
|
303
|
+
separator: str = _DEFAULT_SEPARATOR,
|
|
310
304
|
) -> list[str]:
|
|
311
305
|
brackets = list(brackets)
|
|
312
306
|
opens, closes = transpose(brackets)
|
|
@@ -397,7 +391,7 @@ class _SplitStrOpeningBracketUnmatchedError(SplitStrError):
|
|
|
397
391
|
|
|
398
392
|
|
|
399
393
|
def join_strs(
|
|
400
|
-
texts: Iterable[str], /, *, sort: bool = False, separator: str =
|
|
394
|
+
texts: Iterable[str], /, *, sort: bool = False, separator: str = _DEFAULT_SEPARATOR
|
|
401
395
|
) -> str:
|
|
402
396
|
"""Join a collection of strings, with a special provision for the empty list."""
|
|
403
397
|
texts = list(texts)
|
|
@@ -410,7 +404,7 @@ def join_strs(
|
|
|
410
404
|
return separator.join(texts)
|
|
411
405
|
|
|
412
406
|
|
|
413
|
-
def _escape_separator(*, separator: str =
|
|
407
|
+
def _escape_separator(*, separator: str = _DEFAULT_SEPARATOR) -> str:
|
|
414
408
|
return f"\\{separator}"
|
|
415
409
|
|
|
416
410
|
|
|
@@ -513,9 +507,30 @@ def unique_str() -> str:
|
|
|
513
507
|
return f"{now}_{pid}_{ident}_{key}"
|
|
514
508
|
|
|
515
509
|
|
|
510
|
+
##
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def _kebab_snake_case(text: str, separator: str, /) -> str:
|
|
514
|
+
"""Convert text into kebab/snake case."""
|
|
515
|
+
leading = bool(search(r"^_", text))
|
|
516
|
+
trailing = bool(search(r"_$", text))
|
|
517
|
+
parts = _SPLIT_TEXT.findall(text)
|
|
518
|
+
parts = (p for p in parts if len(p) >= 1)
|
|
519
|
+
parts = chain([""] if leading else [], parts, [""] if trailing else [])
|
|
520
|
+
return separator.join(parts).lower()
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
_SPLIT_TEXT = re.compile(
|
|
524
|
+
r"""
|
|
525
|
+
[A-Z]+(?=[A-Z][a-z0-9]) | # all caps followed by Upper+lower or digit (API in APIResponse2)
|
|
526
|
+
[A-Z]?[a-z]+[0-9]* | # normal words with optional trailing digits (Text123)
|
|
527
|
+
[A-Z]+[0-9]* | # consecutive caps with optional trailing digits (ID2)
|
|
528
|
+
""",
|
|
529
|
+
flags=VERBOSE,
|
|
530
|
+
)
|
|
531
|
+
|
|
516
532
|
__all__ = [
|
|
517
533
|
"BRACKETS",
|
|
518
|
-
"DEFAULT_SEPARATOR",
|
|
519
534
|
"LIST_SEPARATOR",
|
|
520
535
|
"PAIR_SEPARATOR",
|
|
521
536
|
"ParseBoolError",
|
|
@@ -523,6 +538,7 @@ __all__ = [
|
|
|
523
538
|
"SplitKeyValuePairsError",
|
|
524
539
|
"SplitStrError",
|
|
525
540
|
"join_strs",
|
|
541
|
+
"kebab_case",
|
|
526
542
|
"parse_bool",
|
|
527
543
|
"parse_none",
|
|
528
544
|
"pascal_case",
|
utilities/timer.py
CHANGED
|
@@ -56,11 +56,11 @@ class Timer:
|
|
|
56
56
|
|
|
57
57
|
@override
|
|
58
58
|
def __repr__(self) -> str:
|
|
59
|
-
return self.timedelta.
|
|
59
|
+
return self.timedelta.format_iso()
|
|
60
60
|
|
|
61
61
|
@override
|
|
62
62
|
def __str__(self) -> str:
|
|
63
|
-
return self.timedelta.
|
|
63
|
+
return self.timedelta.format_iso()
|
|
64
64
|
|
|
65
65
|
# comparison
|
|
66
66
|
|
utilities/traceback.py
CHANGED
|
@@ -98,7 +98,7 @@ def _yield_header_lines(
|
|
|
98
98
|
yield f"Date/time | {format_compact(now)}"
|
|
99
99
|
start_use = to_zoned_date_time(start).to_tz(LOCAL_TIME_ZONE_NAME)
|
|
100
100
|
yield f"Started | {format_compact(start_use)}"
|
|
101
|
-
yield f"Duration | {(now - start_use).
|
|
101
|
+
yield f"Duration | {(now - start_use).format_iso()}"
|
|
102
102
|
yield f"User | {getuser()}"
|
|
103
103
|
yield f"Host | {gethostname()}"
|
|
104
104
|
yield f"Process ID | {getpid()}"
|
utilities/types.py
CHANGED
|
@@ -231,6 +231,10 @@ type Seed = int | float | str | bytes | bytearray | Random
|
|
|
231
231
|
type PatternLike = MaybeStr[Pattern[str]]
|
|
232
232
|
|
|
233
233
|
|
|
234
|
+
# retry
|
|
235
|
+
type Retry = tuple[int, Delta | None]
|
|
236
|
+
|
|
237
|
+
|
|
234
238
|
# text
|
|
235
239
|
type MaybeCallableStr = MaybeCallable[str]
|
|
236
240
|
|
|
@@ -332,6 +336,7 @@ __all__ = [
|
|
|
332
336
|
"PathLike",
|
|
333
337
|
"PatternLike",
|
|
334
338
|
"PlainDateTimeLike",
|
|
339
|
+
"Retry",
|
|
335
340
|
"Seed",
|
|
336
341
|
"SequenceStr",
|
|
337
342
|
"SerializeObjectExtra",
|
utilities/typing.py
CHANGED
|
@@ -42,7 +42,13 @@ from whenever import (
|
|
|
42
42
|
|
|
43
43
|
from utilities.iterables import unique_everseen
|
|
44
44
|
from utilities.sentinel import Sentinel
|
|
45
|
-
from utilities.types import
|
|
45
|
+
from utilities.types import (
|
|
46
|
+
Dataclass,
|
|
47
|
+
StrMapping,
|
|
48
|
+
StrStrMapping,
|
|
49
|
+
TupleOrStrMapping,
|
|
50
|
+
TypeLike,
|
|
51
|
+
)
|
|
46
52
|
|
|
47
53
|
|
|
48
54
|
def get_args(obj: Any, /, *, optional_drop_none: bool = False) -> tuple[Any, ...]:
|
|
@@ -58,7 +64,7 @@ def get_args(obj: Any, /, *, optional_drop_none: bool = False) -> tuple[Any, ...
|
|
|
58
64
|
##
|
|
59
65
|
|
|
60
66
|
|
|
61
|
-
def get_forward_ref_args(obj: Any, /) ->
|
|
67
|
+
def get_forward_ref_args(obj: Any, /) -> StrStrMapping:
|
|
62
68
|
"""Get the forward args."""
|
|
63
69
|
return {
|
|
64
70
|
k: v.__forward_arg__
|
utilities/whenever.py
CHANGED
|
@@ -433,6 +433,36 @@ TODAY_LOCAL = get_today_local()
|
|
|
433
433
|
##
|
|
434
434
|
|
|
435
435
|
|
|
436
|
+
def is_weekend(
|
|
437
|
+
date_time: ZonedDateTime,
|
|
438
|
+
/,
|
|
439
|
+
*,
|
|
440
|
+
start: tuple[Weekday, Time] = (Weekday.SATURDAY, Time.MIN),
|
|
441
|
+
end: tuple[Weekday, Time] = (Weekday.SUNDAY, Time.MAX),
|
|
442
|
+
) -> bool:
|
|
443
|
+
"""Check if a datetime is in the weekend."""
|
|
444
|
+
weekday, time = date_time.date().day_of_week(), date_time.time()
|
|
445
|
+
start_weekday, start_time = start
|
|
446
|
+
end_weekday, end_time = end
|
|
447
|
+
if start_weekday.value == end_weekday.value:
|
|
448
|
+
return start_time <= time <= end_time
|
|
449
|
+
if start_weekday.value < end_weekday.value:
|
|
450
|
+
return (
|
|
451
|
+
((weekday == start_weekday) and (time >= start_time))
|
|
452
|
+
or (start_weekday.value < weekday.value < end_weekday.value)
|
|
453
|
+
or ((weekday == end_weekday) and (time <= end_time))
|
|
454
|
+
)
|
|
455
|
+
return (
|
|
456
|
+
((weekday == start_weekday) and (time >= start_time))
|
|
457
|
+
or (weekday.value > start_weekday.value)
|
|
458
|
+
or (weekday.value < end_weekday.value)
|
|
459
|
+
or ((weekday == end_weekday) and (time <= end_time))
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
##
|
|
464
|
+
|
|
465
|
+
|
|
436
466
|
def mean_datetime(
|
|
437
467
|
datetimes: Iterable[ZonedDateTime],
|
|
438
468
|
/,
|
|
@@ -892,7 +922,7 @@ def to_date(
|
|
|
892
922
|
case None:
|
|
893
923
|
return get_today(time_zone)
|
|
894
924
|
case str():
|
|
895
|
-
return Date.
|
|
925
|
+
return Date.parse_iso(date)
|
|
896
926
|
case dt.date():
|
|
897
927
|
return Date.from_py_date(date)
|
|
898
928
|
case Callable() as func:
|
|
@@ -1475,7 +1505,7 @@ def to_time(
|
|
|
1475
1505
|
case None:
|
|
1476
1506
|
return get_time(time_zone)
|
|
1477
1507
|
case str():
|
|
1478
|
-
return Time.
|
|
1508
|
+
return Time.parse_iso(time)
|
|
1479
1509
|
case dt.time():
|
|
1480
1510
|
return Time.from_py_time(time)
|
|
1481
1511
|
case Callable() as func:
|
|
@@ -1703,7 +1733,7 @@ def to_zoned_date_time(
|
|
|
1703
1733
|
case None:
|
|
1704
1734
|
return get_now(UTC if time_zone is None else time_zone)
|
|
1705
1735
|
case str() as text:
|
|
1706
|
-
date_time_use = ZonedDateTime.
|
|
1736
|
+
date_time_use = ZonedDateTime.parse_iso(text.replace("~", "/"))
|
|
1707
1737
|
case dt.datetime() as py_date_time:
|
|
1708
1738
|
if isinstance(date_time.tzinfo, ZoneInfo):
|
|
1709
1739
|
py_date_time_use = py_date_time
|
|
@@ -1767,7 +1797,7 @@ class WheneverLogRecord(LogRecord):
|
|
|
1767
1797
|
name, level, pathname, lineno, msg, args, exc_info, func, sinfo
|
|
1768
1798
|
)
|
|
1769
1799
|
length = self._get_length()
|
|
1770
|
-
plain = format(get_now_local().to_plain().
|
|
1800
|
+
plain = format(get_now_local().to_plain().format_iso(), f"{length}s")
|
|
1771
1801
|
self.zoned_datetime = f"{plain}[{LOCAL_TIME_ZONE_NAME}]"
|
|
1772
1802
|
|
|
1773
1803
|
@classmethod
|
|
@@ -1775,7 +1805,7 @@ class WheneverLogRecord(LogRecord):
|
|
|
1775
1805
|
def _get_length(cls) -> int:
|
|
1776
1806
|
"""Get maximum length of a formatted string."""
|
|
1777
1807
|
now = get_now_local().replace(nanosecond=1000).to_plain()
|
|
1778
|
-
return len(now.
|
|
1808
|
+
return len(now.format_iso())
|
|
1779
1809
|
|
|
1780
1810
|
|
|
1781
1811
|
##
|
|
@@ -2033,6 +2063,7 @@ __all__ = [
|
|
|
2033
2063
|
"get_time_local",
|
|
2034
2064
|
"get_today",
|
|
2035
2065
|
"get_today_local",
|
|
2066
|
+
"is_weekend",
|
|
2036
2067
|
"mean_datetime",
|
|
2037
2068
|
"min_max_date",
|
|
2038
2069
|
"round_date_or_date_time",
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: dycw-utilities
|
|
3
|
-
Version: 0.166.30
|
|
4
|
-
Author-email: Derek Wan <d.wan@icloud.com>
|
|
5
|
-
License-File: LICENSE
|
|
6
|
-
Requires-Python: >=3.12
|
|
7
|
-
Requires-Dist: atomicwrites<1.5,>=1.4.1
|
|
8
|
-
Requires-Dist: typing-extensions<4.16,>=4.15.0
|
|
9
|
-
Requires-Dist: tzlocal<5.4,>=5.3.1
|
|
10
|
-
Requires-Dist: whenever<0.9,>=0.8.8
|
|
11
|
-
Provides-Extra: logging
|
|
12
|
-
Requires-Dist: coloredlogs<15.1,>=15.0.1; extra == 'logging'
|
|
13
|
-
Provides-Extra: test
|
|
14
|
-
Requires-Dist: dycw-pytest-only<2.2,>=2.1.1; extra == 'test'
|
|
15
|
-
Requires-Dist: hypothesis<6.139,>=6.138.16; extra == 'test'
|
|
16
|
-
Requires-Dist: pytest-asyncio<1.3,>=1.2.0; extra == 'test'
|
|
17
|
-
Requires-Dist: pytest-cov<7.1,>=7.0.0; extra == 'test'
|
|
18
|
-
Requires-Dist: pytest-instafail<0.6,>=0.5.0; extra == 'test'
|
|
19
|
-
Requires-Dist: pytest-lazy-fixtures<1.4,>=1.3.4; extra == 'test'
|
|
20
|
-
Requires-Dist: pytest-randomly<4.1,>=4.0.1; extra == 'test'
|
|
21
|
-
Requires-Dist: pytest-regressions<2.9,>=2.8.3; extra == 'test'
|
|
22
|
-
Requires-Dist: pytest-repeat<0.10,>=0.9.4; extra == 'test'
|
|
23
|
-
Requires-Dist: pytest-rerunfailures<16.1,>=16.0.1; extra == 'test'
|
|
24
|
-
Requires-Dist: pytest-rng<1.1,>=1.0.0; extra == 'test'
|
|
25
|
-
Requires-Dist: pytest-timeout<2.5,>=2.4.0; extra == 'test'
|
|
26
|
-
Requires-Dist: pytest-xdist<3.9,>=3.8.0; extra == 'test'
|
|
27
|
-
Requires-Dist: pytest<8.5,>=8.4.2; extra == 'test'
|
|
28
|
-
Requires-Dist: testbook<0.5,>=0.4.2; extra == 'test'
|
|
29
|
-
Description-Content-Type: text/markdown
|
|
30
|
-
|
|
31
|
-
[](https://badge.fury.io/py/dycw-utilities)
|
|
32
|
-
|
|
33
|
-
# `dycw-utilities`
|
|
34
|
-
|
|
35
|
-
[All the Python functions I don't want to write twice.](https://github.com/nvim-lua/plenary.nvim)
|
|
36
|
-
|
|
37
|
-
## Installation
|
|
38
|
-
|
|
39
|
-
- `pip install dycw-utilities`
|
|
40
|
-
|
|
41
|
-
or with [extras](https://github.com/dycw/python-utilities/blob/master/pyproject.toml).
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2024 Derek Wan
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|