dycw-utilities 0.146.2__py3-none-any.whl → 0.178.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dycw-utilities might be problematic. Click here for more details.
- dycw_utilities-0.178.1.dist-info/METADATA +34 -0
- dycw_utilities-0.178.1.dist-info/RECORD +105 -0
- dycw_utilities-0.178.1.dist-info/WHEEL +4 -0
- {dycw_utilities-0.146.2.dist-info → dycw_utilities-0.178.1.dist-info}/entry_points.txt +1 -0
- utilities/__init__.py +1 -1
- utilities/altair.py +10 -7
- utilities/asyncio.py +129 -50
- utilities/atomicwrites.py +1 -1
- utilities/atools.py +64 -4
- utilities/cachetools.py +9 -6
- utilities/click.py +144 -49
- utilities/concurrent.py +1 -1
- utilities/contextlib.py +4 -2
- utilities/contextvars.py +20 -1
- utilities/cryptography.py +3 -3
- utilities/dataclasses.py +15 -28
- utilities/docker.py +387 -0
- utilities/enum.py +2 -2
- utilities/errors.py +17 -3
- utilities/fastapi.py +8 -3
- utilities/fpdf2.py +2 -2
- utilities/functions.py +20 -297
- utilities/git.py +19 -0
- utilities/grp.py +28 -0
- utilities/hypothesis.py +361 -79
- utilities/importlib.py +17 -1
- utilities/inflect.py +1 -1
- utilities/iterables.py +33 -58
- utilities/jinja2.py +148 -0
- utilities/json.py +1 -1
- utilities/libcst.py +7 -7
- utilities/logging.py +131 -93
- utilities/math.py +8 -4
- utilities/more_itertools.py +4 -6
- utilities/operator.py +1 -1
- utilities/orjson.py +86 -34
- utilities/os.py +49 -2
- utilities/packaging.py +115 -0
- utilities/parse.py +2 -2
- utilities/pathlib.py +66 -34
- utilities/permissions.py +298 -0
- utilities/platform.py +5 -4
- utilities/polars.py +934 -420
- utilities/polars_ols.py +1 -1
- utilities/postgres.py +317 -153
- utilities/pottery.py +10 -86
- utilities/pqdm.py +3 -3
- utilities/pwd.py +28 -0
- utilities/pydantic.py +4 -51
- utilities/pydantic_settings.py +240 -0
- utilities/pydantic_settings_sops.py +76 -0
- utilities/pyinstrument.py +5 -5
- utilities/pytest.py +100 -126
- utilities/pytest_plugins/pytest_randomly.py +1 -1
- utilities/pytest_plugins/pytest_regressions.py +7 -3
- utilities/pytest_regressions.py +27 -8
- utilities/random.py +11 -6
- utilities/re.py +1 -1
- utilities/redis.py +101 -64
- utilities/sentinel.py +10 -0
- utilities/shelve.py +4 -1
- utilities/shutil.py +25 -0
- utilities/slack_sdk.py +9 -4
- utilities/sqlalchemy.py +422 -352
- utilities/sqlalchemy_polars.py +28 -52
- utilities/string.py +1 -1
- utilities/subprocess.py +1977 -0
- utilities/tempfile.py +112 -4
- utilities/testbook.py +50 -0
- utilities/text.py +174 -42
- utilities/throttle.py +158 -0
- utilities/timer.py +2 -2
- utilities/traceback.py +59 -38
- utilities/types.py +68 -22
- utilities/typing.py +479 -19
- utilities/uuid.py +42 -5
- utilities/version.py +27 -26
- utilities/whenever.py +663 -178
- utilities/zoneinfo.py +80 -22
- dycw_utilities-0.146.2.dist-info/METADATA +0 -41
- dycw_utilities-0.146.2.dist-info/RECORD +0 -99
- dycw_utilities-0.146.2.dist-info/WHEEL +0 -4
- dycw_utilities-0.146.2.dist-info/licenses/LICENSE +0 -21
- utilities/aiolimiter.py +0 -25
- utilities/eventkit.py +0 -388
- utilities/period.py +0 -237
- utilities/python_dotenv.py +0 -101
- utilities/streamlit.py +0 -105
- utilities/typed_settings.py +0 -144
utilities/logging.py
CHANGED
|
@@ -8,6 +8,7 @@ from logging import (
|
|
|
8
8
|
Formatter,
|
|
9
9
|
Handler,
|
|
10
10
|
Logger,
|
|
11
|
+
LoggerAdapter,
|
|
11
12
|
LogRecord,
|
|
12
13
|
StreamHandler,
|
|
13
14
|
basicConfig,
|
|
@@ -18,9 +19,11 @@ from logging import (
|
|
|
18
19
|
from logging.handlers import BaseRotatingHandler, TimedRotatingFileHandler
|
|
19
20
|
from pathlib import Path
|
|
20
21
|
from re import Pattern
|
|
22
|
+
from socket import gethostname
|
|
21
23
|
from typing import (
|
|
22
24
|
TYPE_CHECKING,
|
|
23
25
|
Any,
|
|
26
|
+
Concatenate,
|
|
24
27
|
Literal,
|
|
25
28
|
NotRequired,
|
|
26
29
|
Self,
|
|
@@ -30,13 +33,13 @@ from typing import (
|
|
|
30
33
|
override,
|
|
31
34
|
)
|
|
32
35
|
|
|
33
|
-
from whenever import
|
|
36
|
+
from whenever import ZonedDateTime
|
|
34
37
|
|
|
35
38
|
from utilities.atomicwrites import move_many
|
|
36
39
|
from utilities.dataclasses import replace_non_sentinel
|
|
37
40
|
from utilities.errors import ImpossibleCaseError
|
|
38
41
|
from utilities.iterables import OneEmptyError, always_iterable, one
|
|
39
|
-
from utilities.pathlib import ensure_suffix,
|
|
42
|
+
from utilities.pathlib import ensure_suffix, to_path
|
|
40
43
|
from utilities.re import (
|
|
41
44
|
ExtractGroupError,
|
|
42
45
|
ExtractGroupsError,
|
|
@@ -44,31 +47,28 @@ from utilities.re import (
|
|
|
44
47
|
extract_groups,
|
|
45
48
|
)
|
|
46
49
|
from utilities.sentinel import Sentinel, sentinel
|
|
47
|
-
from utilities.tzlocal import LOCAL_TIME_ZONE_NAME
|
|
48
50
|
from utilities.whenever import (
|
|
49
51
|
WheneverLogRecord,
|
|
50
52
|
format_compact,
|
|
51
53
|
get_now_local,
|
|
52
|
-
|
|
54
|
+
to_zoned_date_time,
|
|
53
55
|
)
|
|
54
56
|
|
|
55
57
|
if TYPE_CHECKING:
|
|
56
|
-
from collections.abc import Callable, Iterable, Mapping
|
|
58
|
+
from collections.abc import Callable, Iterable, Mapping, MutableMapping
|
|
57
59
|
from datetime import time
|
|
58
60
|
from logging import _FilterType
|
|
59
61
|
|
|
60
62
|
from utilities.types import (
|
|
61
|
-
|
|
63
|
+
LoggerLike,
|
|
62
64
|
LogLevel,
|
|
63
65
|
MaybeCallablePathLike,
|
|
64
66
|
MaybeIterable,
|
|
65
67
|
PathLike,
|
|
68
|
+
StrMapping,
|
|
66
69
|
)
|
|
67
70
|
|
|
68
71
|
|
|
69
|
-
_DEFAULT_FORMAT = (
|
|
70
|
-
"{zoned_datetime} | {name}:{funcName}:{lineno} | {levelname:8} | {message}"
|
|
71
|
-
)
|
|
72
72
|
_DEFAULT_DATEFMT = "%Y-%m-%d %H:%M:%S"
|
|
73
73
|
_DEFAULT_BACKUP_COUNT: int = 100
|
|
74
74
|
_DEFAULT_MAX_BYTES: int = 10 * 1024**2
|
|
@@ -78,6 +78,35 @@ _DEFAULT_WHEN: _When = "D"
|
|
|
78
78
|
##
|
|
79
79
|
|
|
80
80
|
|
|
81
|
+
def add_adapter[**P](
|
|
82
|
+
logger: Logger,
|
|
83
|
+
process: Callable[Concatenate[str, P], str],
|
|
84
|
+
/,
|
|
85
|
+
*args: P.args,
|
|
86
|
+
**kwargs: P.kwargs,
|
|
87
|
+
) -> LoggerAdapter:
|
|
88
|
+
"""Add an adapter to a logger."""
|
|
89
|
+
|
|
90
|
+
class CustomAdapter(LoggerAdapter):
|
|
91
|
+
@override
|
|
92
|
+
def process(
|
|
93
|
+
self, msg: str, kwargs: MutableMapping[str, Any]
|
|
94
|
+
) -> tuple[str, MutableMapping[str, Any]]:
|
|
95
|
+
extra = cast("_ArgsAndKwargs", self.extra)
|
|
96
|
+
new_msg = process(msg, *extra["args"], **extra["kwargs"])
|
|
97
|
+
return new_msg, kwargs
|
|
98
|
+
|
|
99
|
+
return CustomAdapter(logger, extra=_ArgsAndKwargs(args=args, kwargs=kwargs))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class _ArgsAndKwargs(TypedDict):
|
|
103
|
+
args: tuple[Any, ...]
|
|
104
|
+
kwargs: StrMapping
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
##
|
|
108
|
+
|
|
109
|
+
|
|
81
110
|
def add_filters(handler: Handler, /, *filters: _FilterType) -> None:
|
|
82
111
|
"""Add a set of filters to a handler."""
|
|
83
112
|
for filter_i in filters:
|
|
@@ -89,8 +118,10 @@ def add_filters(handler: Handler, /, *filters: _FilterType) -> None:
|
|
|
89
118
|
|
|
90
119
|
def basic_config(
|
|
91
120
|
*,
|
|
92
|
-
obj:
|
|
93
|
-
format_: str =
|
|
121
|
+
obj: LoggerLike | Handler | None = None,
|
|
122
|
+
format_: str | None = None,
|
|
123
|
+
prefix: str | None = None,
|
|
124
|
+
hostname: bool = False,
|
|
94
125
|
datefmt: str = _DEFAULT_DATEFMT,
|
|
95
126
|
level: LogLevel = "INFO",
|
|
96
127
|
filters: MaybeIterable[_FilterType] | None = None,
|
|
@@ -100,13 +131,19 @@ def basic_config(
|
|
|
100
131
|
"""Do the basic config."""
|
|
101
132
|
match obj:
|
|
102
133
|
case None:
|
|
103
|
-
|
|
134
|
+
if format_ is None:
|
|
135
|
+
format_use = get_format_str(prefix=prefix, hostname=hostname)
|
|
136
|
+
else:
|
|
137
|
+
format_use = format_
|
|
138
|
+
basicConfig(format=format_use, datefmt=datefmt, style="{", level=level)
|
|
104
139
|
case Logger() as logger:
|
|
105
140
|
logger.setLevel(level)
|
|
106
141
|
logger.addHandler(handler := StreamHandler())
|
|
107
142
|
basic_config(
|
|
108
143
|
obj=handler,
|
|
109
144
|
format_=format_,
|
|
145
|
+
prefix=prefix,
|
|
146
|
+
hostname=hostname,
|
|
110
147
|
datefmt=datefmt,
|
|
111
148
|
level=level,
|
|
112
149
|
filters=filters,
|
|
@@ -115,8 +152,10 @@ def basic_config(
|
|
|
115
152
|
)
|
|
116
153
|
case str() as name:
|
|
117
154
|
basic_config(
|
|
118
|
-
obj=
|
|
155
|
+
obj=to_logger(name),
|
|
119
156
|
format_=format_,
|
|
157
|
+
prefix=prefix,
|
|
158
|
+
hostname=hostname,
|
|
120
159
|
datefmt=datefmt,
|
|
121
160
|
level=level,
|
|
122
161
|
filters=filters,
|
|
@@ -128,50 +167,32 @@ def basic_config(
|
|
|
128
167
|
if filters is not None:
|
|
129
168
|
add_filters(handler, *always_iterable(filters))
|
|
130
169
|
formatter = get_formatter(
|
|
170
|
+
prefix=prefix,
|
|
131
171
|
format_=format_,
|
|
172
|
+
hostname=hostname,
|
|
132
173
|
datefmt=datefmt,
|
|
133
174
|
plain=plain,
|
|
134
175
|
color_field_styles=color_field_styles,
|
|
135
176
|
)
|
|
136
177
|
handler.setFormatter(formatter)
|
|
137
|
-
case
|
|
178
|
+
case never:
|
|
138
179
|
assert_never(never)
|
|
139
180
|
|
|
140
181
|
|
|
141
182
|
##
|
|
142
183
|
|
|
143
184
|
|
|
144
|
-
def
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return default
|
|
156
|
-
return bool(value)
|
|
157
|
-
|
|
158
|
-
return filter_
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
# fmt: off
|
|
162
|
-
_FILTER_FOR_KEY_BLACKLIST = {
|
|
163
|
-
"args", "created", "exc_info", "exc_text", "filename", "funcName", "getMessage", "levelname", "levelno", "lineno", "module", "msecs", "msg", "name", "pathname", "process", "processName", "relativeCreated", "stack_info", "taskName", "thread", "threadName"
|
|
164
|
-
}
|
|
165
|
-
# fmt: on
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
@dataclass(kw_only=True, slots=True)
|
|
169
|
-
class FilterForKeyError(Exception):
|
|
170
|
-
key: str
|
|
171
|
-
|
|
172
|
-
@override
|
|
173
|
-
def __str__(self) -> str:
|
|
174
|
-
return f"Invalid key: {self.key!r}"
|
|
185
|
+
def get_format_str(*, prefix: str | None = None, hostname: bool = False) -> str:
|
|
186
|
+
"""Generate a format string."""
|
|
187
|
+
parts: list[str] = [
|
|
188
|
+
"{zoned_datetime}",
|
|
189
|
+
f"{gethostname()}:{{process}}" if hostname else "{process}",
|
|
190
|
+
"{name}:{funcName}:{lineno}",
|
|
191
|
+
"{levelname}",
|
|
192
|
+
"{message}",
|
|
193
|
+
]
|
|
194
|
+
joined = " | ".join(parts)
|
|
195
|
+
return joined if prefix is None else f"{prefix} {joined}"
|
|
175
196
|
|
|
176
197
|
|
|
177
198
|
##
|
|
@@ -189,7 +210,9 @@ class _FieldStyleDict(TypedDict):
|
|
|
189
210
|
|
|
190
211
|
def get_formatter(
|
|
191
212
|
*,
|
|
192
|
-
format_: str =
|
|
213
|
+
format_: str | None = None,
|
|
214
|
+
prefix: str | None = None,
|
|
215
|
+
hostname: bool = False,
|
|
193
216
|
datefmt: str = _DEFAULT_DATEFMT,
|
|
194
217
|
plain: bool = False,
|
|
195
218
|
color_field_styles: Mapping[str, _FieldStyleKeys] | None = None,
|
|
@@ -197,40 +220,44 @@ def get_formatter(
|
|
|
197
220
|
"""Get the formatter; colored if available."""
|
|
198
221
|
setLogRecordFactory(WheneverLogRecord)
|
|
199
222
|
if plain:
|
|
200
|
-
return _get_plain_formatter(
|
|
223
|
+
return _get_plain_formatter(
|
|
224
|
+
format_=format_, prefix=prefix, hostname=hostname, datefmt=datefmt
|
|
225
|
+
)
|
|
201
226
|
try:
|
|
202
227
|
from coloredlogs import DEFAULT_FIELD_STYLES, ColoredFormatter
|
|
203
228
|
except ModuleNotFoundError: # pragma: no cover
|
|
204
|
-
return _get_plain_formatter(
|
|
229
|
+
return _get_plain_formatter(
|
|
230
|
+
format_=format_, prefix=prefix, hostname=hostname, datefmt=datefmt
|
|
231
|
+
)
|
|
232
|
+
format_use = (
|
|
233
|
+
get_format_str(prefix=prefix, hostname=hostname) if format_ is None else format_
|
|
234
|
+
)
|
|
205
235
|
default = cast("dict[_FieldStyleKeys, _FieldStyleDict]", DEFAULT_FIELD_STYLES)
|
|
206
236
|
field_styles = {cast("str", k): v for k, v in default.items()}
|
|
207
237
|
field_styles["zoned_datetime"] = default["asctime"]
|
|
238
|
+
field_styles["hostname"] = default["hostname"]
|
|
239
|
+
field_styles["process"] = default["hostname"]
|
|
240
|
+
field_styles["lineno"] = default["name"]
|
|
241
|
+
field_styles["funcName"] = default["name"]
|
|
208
242
|
if color_field_styles is not None:
|
|
209
243
|
field_styles.update({k: default[v] for k, v in color_field_styles.items()})
|
|
210
244
|
return ColoredFormatter(
|
|
211
|
-
fmt=
|
|
245
|
+
fmt=format_use, datefmt=datefmt, style="{", field_styles=field_styles
|
|
212
246
|
)
|
|
213
247
|
|
|
214
248
|
|
|
215
249
|
def _get_plain_formatter(
|
|
216
|
-
*,
|
|
250
|
+
*,
|
|
251
|
+
format_: str | None = None,
|
|
252
|
+
prefix: str | None = None,
|
|
253
|
+
hostname: bool = False,
|
|
254
|
+
datefmt: str = _DEFAULT_DATEFMT,
|
|
217
255
|
) -> Formatter:
|
|
218
256
|
"""Get the plain formatter."""
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
def get_logger(*, logger: LoggerOrName | None = None) -> Logger:
|
|
226
|
-
"""Get a logger."""
|
|
227
|
-
match logger:
|
|
228
|
-
case Logger():
|
|
229
|
-
return logger
|
|
230
|
-
case str() | None:
|
|
231
|
-
return getLogger(logger)
|
|
232
|
-
case _ as never:
|
|
233
|
-
assert_never(never)
|
|
257
|
+
format_use = (
|
|
258
|
+
get_format_str(prefix=prefix, hostname=hostname) if format_ is None else format_
|
|
259
|
+
)
|
|
260
|
+
return Formatter(fmt=format_use, datefmt=datefmt, style="{")
|
|
234
261
|
|
|
235
262
|
|
|
236
263
|
##
|
|
@@ -259,13 +286,13 @@ class GetLoggingLevelNumberError(Exception):
|
|
|
259
286
|
|
|
260
287
|
def setup_logging(
|
|
261
288
|
*,
|
|
262
|
-
logger:
|
|
263
|
-
format_: str =
|
|
289
|
+
logger: LoggerLike | None = None,
|
|
290
|
+
format_: str | None = None,
|
|
264
291
|
datefmt: str = _DEFAULT_DATEFMT,
|
|
265
292
|
console_level: LogLevel = "INFO",
|
|
266
293
|
console_prefix: str = "❯", # noqa: RUF001
|
|
267
294
|
console_filters: MaybeIterable[_FilterType] | None = None,
|
|
268
|
-
files_dir: MaybeCallablePathLike
|
|
295
|
+
files_dir: MaybeCallablePathLike = Path.cwd,
|
|
269
296
|
files_max_bytes: int = _DEFAULT_MAX_BYTES,
|
|
270
297
|
files_when: _When = _DEFAULT_WHEN,
|
|
271
298
|
files_interval: int = 1,
|
|
@@ -275,20 +302,20 @@ def setup_logging(
|
|
|
275
302
|
"""Set up logger."""
|
|
276
303
|
basic_config(
|
|
277
304
|
obj=logger,
|
|
278
|
-
|
|
305
|
+
prefix=console_prefix,
|
|
306
|
+
format_=format_,
|
|
279
307
|
datefmt=datefmt,
|
|
280
308
|
level=console_level,
|
|
281
309
|
filters=console_filters,
|
|
282
310
|
)
|
|
283
|
-
logger_use =
|
|
311
|
+
logger_use = to_logger(logger)
|
|
284
312
|
name = logger_use.name
|
|
285
|
-
dir_ = get_path(path=files_dir)
|
|
286
313
|
levels: list[LogLevel] = ["DEBUG", "INFO", "ERROR"]
|
|
287
314
|
for level in levels:
|
|
288
315
|
lower = level.lower()
|
|
289
316
|
for stem in [lower, f"{name}-{lower}"]:
|
|
290
317
|
handler = SizeAndTimeRotatingFileHandler(
|
|
291
|
-
|
|
318
|
+
to_path(files_dir).joinpath(stem).with_suffix(".txt"),
|
|
292
319
|
maxBytes=files_max_bytes,
|
|
293
320
|
when=files_when,
|
|
294
321
|
interval=files_interval,
|
|
@@ -298,6 +325,7 @@ def setup_logging(
|
|
|
298
325
|
basic_config(
|
|
299
326
|
obj=handler,
|
|
300
327
|
format_=format_,
|
|
328
|
+
hostname=True,
|
|
301
329
|
datefmt=datefmt,
|
|
302
330
|
level=level,
|
|
303
331
|
filters=files_filters,
|
|
@@ -359,9 +387,7 @@ class SizeAndTimeRotatingFileHandler(BaseRotatingHandler):
|
|
|
359
387
|
def emit(self, record: LogRecord) -> None:
|
|
360
388
|
try:
|
|
361
389
|
if (self._backup_count is not None) and self._should_rollover(record):
|
|
362
|
-
self._do_rollover(
|
|
363
|
-
backup_count=self._backup_count
|
|
364
|
-
)
|
|
390
|
+
self._do_rollover(backup_count=self._backup_count)
|
|
365
391
|
FileHandler.emit(self, record)
|
|
366
392
|
except Exception: # noqa: BLE001 # pragma: no cover
|
|
367
393
|
self.handleError(record)
|
|
@@ -371,23 +397,23 @@ class SizeAndTimeRotatingFileHandler(BaseRotatingHandler):
|
|
|
371
397
|
self.stream.close()
|
|
372
398
|
self.stream = None
|
|
373
399
|
|
|
374
|
-
actions = _compute_rollover_actions(
|
|
400
|
+
actions = _compute_rollover_actions(
|
|
375
401
|
self._directory,
|
|
376
402
|
self._stem,
|
|
377
403
|
self._suffix,
|
|
378
404
|
patterns=self._patterns,
|
|
379
405
|
backup_count=backup_count,
|
|
380
406
|
)
|
|
381
|
-
actions.do()
|
|
407
|
+
actions.do()
|
|
382
408
|
|
|
383
409
|
if not self.delay: # pragma: no cover
|
|
384
410
|
self.stream = self._open()
|
|
385
|
-
self._time_handler.rolloverAt = (
|
|
386
|
-
|
|
411
|
+
self._time_handler.rolloverAt = self._time_handler.computeRollover(
|
|
412
|
+
get_now_local().timestamp()
|
|
387
413
|
)
|
|
388
414
|
|
|
389
415
|
def _should_rollover(self, record: LogRecord, /) -> bool:
|
|
390
|
-
if self._max_bytes is not None:
|
|
416
|
+
if self._max_bytes is not None:
|
|
391
417
|
try:
|
|
392
418
|
size = self._filename.stat().st_size
|
|
393
419
|
except FileNotFoundError:
|
|
@@ -395,14 +421,14 @@ class SizeAndTimeRotatingFileHandler(BaseRotatingHandler):
|
|
|
395
421
|
else:
|
|
396
422
|
if size >= self._max_bytes:
|
|
397
423
|
return True
|
|
398
|
-
return bool(self._time_handler.shouldRollover(record))
|
|
424
|
+
return bool(self._time_handler.shouldRollover(record))
|
|
399
425
|
|
|
400
426
|
|
|
401
427
|
def _compute_rollover_patterns(stem: str, suffix: str, /) -> _RolloverPatterns:
|
|
402
428
|
return _RolloverPatterns(
|
|
403
429
|
pattern1=re.compile(rf"^{stem}\.(\d+){suffix}$"),
|
|
404
|
-
pattern2=re.compile(rf"^{stem}\.(\d+)__(
|
|
405
|
-
pattern3=re.compile(rf"^{stem}\.(\d+)__(
|
|
430
|
+
pattern2=re.compile(rf"^{stem}\.(\d+)__(.+?){suffix}$"),
|
|
431
|
+
pattern3=re.compile(rf"^{stem}\.(\d+)__(.+?)__(.+?){suffix}$"),
|
|
406
432
|
)
|
|
407
433
|
|
|
408
434
|
|
|
@@ -502,10 +528,8 @@ class _RotatingLogFile:
|
|
|
502
528
|
stem=stem,
|
|
503
529
|
suffix=suffix,
|
|
504
530
|
index=int(index),
|
|
505
|
-
start=
|
|
506
|
-
|
|
507
|
-
),
|
|
508
|
-
end=PlainDateTime.parse_common_iso(end).assume_tz(LOCAL_TIME_ZONE_NAME),
|
|
531
|
+
start=to_zoned_date_time(start),
|
|
532
|
+
end=to_zoned_date_time(end),
|
|
509
533
|
)
|
|
510
534
|
try:
|
|
511
535
|
index, end = extract_groups(patterns.pattern2, path.name)
|
|
@@ -517,7 +541,7 @@ class _RotatingLogFile:
|
|
|
517
541
|
stem=stem,
|
|
518
542
|
suffix=suffix,
|
|
519
543
|
index=int(index),
|
|
520
|
-
end=
|
|
544
|
+
end=to_zoned_date_time(end),
|
|
521
545
|
)
|
|
522
546
|
try:
|
|
523
547
|
index = extract_group(patterns.pattern1, path.name)
|
|
@@ -538,9 +562,9 @@ class _RotatingLogFile:
|
|
|
538
562
|
case int() as index, None, None:
|
|
539
563
|
tail = str(index)
|
|
540
564
|
case int() as index, None, ZonedDateTime() as end:
|
|
541
|
-
tail = f"{index}__{format_compact(
|
|
565
|
+
tail = f"{index}__{format_compact(end, path=True)}"
|
|
542
566
|
case int() as index, ZonedDateTime() as start, ZonedDateTime() as end:
|
|
543
|
-
tail = f"{index}__{format_compact(
|
|
567
|
+
tail = f"{index}__{format_compact(start, path=True)}__{format_compact(end, path=True)}"
|
|
544
568
|
case _: # pragma: no cover
|
|
545
569
|
raise ImpossibleCaseError(
|
|
546
570
|
case=[f"{self.index=}", f"{self.start=}", f"{self.end=}"]
|
|
@@ -578,14 +602,28 @@ class _Rotation:
|
|
|
578
602
|
return self.file.replace(index=self.index, start=self.start, end=self.end).path
|
|
579
603
|
|
|
580
604
|
|
|
605
|
+
##
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def to_logger(logger: LoggerLike | None = None, /) -> Logger:
|
|
609
|
+
"""Convert to a logger."""
|
|
610
|
+
match logger:
|
|
611
|
+
case Logger():
|
|
612
|
+
return logger
|
|
613
|
+
case str() | None:
|
|
614
|
+
return getLogger(logger)
|
|
615
|
+
case never:
|
|
616
|
+
assert_never(never)
|
|
617
|
+
|
|
618
|
+
|
|
581
619
|
__all__ = [
|
|
582
|
-
"FilterForKeyError",
|
|
583
620
|
"GetLoggingLevelNumberError",
|
|
584
621
|
"SizeAndTimeRotatingFileHandler",
|
|
622
|
+
"add_adapter",
|
|
585
623
|
"add_filters",
|
|
586
624
|
"basic_config",
|
|
587
|
-
"
|
|
588
|
-
"get_logger",
|
|
625
|
+
"get_format_str",
|
|
589
626
|
"get_logging_level_number",
|
|
590
627
|
"setup_logging",
|
|
628
|
+
"to_logger",
|
|
591
629
|
]
|
utilities/math.py
CHANGED
|
@@ -641,7 +641,10 @@ def _is_close(
|
|
|
641
641
|
##
|
|
642
642
|
|
|
643
643
|
|
|
644
|
-
|
|
644
|
+
MAX_DECIMALS = 10
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def number_of_decimals(x: float, /, *, max_decimals: int = MAX_DECIMALS) -> int:
|
|
645
648
|
"""Get the number of decimals."""
|
|
646
649
|
_, frac = divmod(x, 1)
|
|
647
650
|
results = (
|
|
@@ -731,7 +734,7 @@ def round_(
|
|
|
731
734
|
return 0
|
|
732
735
|
case -1:
|
|
733
736
|
return floor(x)
|
|
734
|
-
case
|
|
737
|
+
case never:
|
|
735
738
|
assert_never(never)
|
|
736
739
|
case "standard-tie-floor":
|
|
737
740
|
return _round_tie_standard(x, "floor", rel_tol=rel_tol, abs_tol=abs_tol)
|
|
@@ -743,7 +746,7 @@ def round_(
|
|
|
743
746
|
)
|
|
744
747
|
case "standard-tie-away-zero":
|
|
745
748
|
return _round_tie_standard(x, "away-zero", rel_tol=rel_tol, abs_tol=abs_tol)
|
|
746
|
-
case
|
|
749
|
+
case never:
|
|
747
750
|
assert_never(never)
|
|
748
751
|
|
|
749
752
|
|
|
@@ -876,7 +879,7 @@ def sign(
|
|
|
876
879
|
if is_negative(x, rel_tol=rel_tol, abs_tol=abs_tol):
|
|
877
880
|
return -1
|
|
878
881
|
return 0
|
|
879
|
-
case
|
|
882
|
+
case never:
|
|
880
883
|
assert_never(never)
|
|
881
884
|
|
|
882
885
|
|
|
@@ -889,6 +892,7 @@ def significant_figures(x: float, /, *, n: int = 2) -> str:
|
|
|
889
892
|
|
|
890
893
|
|
|
891
894
|
__all__ = [
|
|
895
|
+
"MAX_DECIMALS",
|
|
892
896
|
"MAX_FLOAT32",
|
|
893
897
|
"MAX_FLOAT64",
|
|
894
898
|
"MAX_INT8",
|
utilities/more_itertools.py
CHANGED
|
@@ -21,7 +21,7 @@ from more_itertools import peekable as _peekable
|
|
|
21
21
|
from utilities.functions import get_class_name
|
|
22
22
|
from utilities.iterables import OneNonUniqueError, one
|
|
23
23
|
from utilities.reprlib import get_repr
|
|
24
|
-
from utilities.sentinel import Sentinel, sentinel
|
|
24
|
+
from utilities.sentinel import Sentinel, is_sentinel, sentinel
|
|
25
25
|
|
|
26
26
|
if TYPE_CHECKING:
|
|
27
27
|
from collections.abc import Iterable, Iterator, Mapping, Sequence
|
|
@@ -206,7 +206,7 @@ def bucket_mapping[T, U, UH: Hashable](
|
|
|
206
206
|
return {k: frozenset(map(pre, v)) for k, v in mapping.items()}
|
|
207
207
|
case Callable(), "unique":
|
|
208
208
|
return _bucket_mapping_unique({k: map(pre, v) for k, v in mapping.items()})
|
|
209
|
-
case
|
|
209
|
+
case never:
|
|
210
210
|
assert_never(never)
|
|
211
211
|
|
|
212
212
|
|
|
@@ -290,9 +290,7 @@ class peekable[T](_peekable): # noqa: N801
|
|
|
290
290
|
def peek[U](self, *, default: U) -> T | U: ...
|
|
291
291
|
@override
|
|
292
292
|
def peek(self, *, default: Any = sentinel) -> Any: # pyright: ignore[reportIncompatibleMethodOverride]
|
|
293
|
-
if
|
|
294
|
-
return super().peek()
|
|
295
|
-
return super().peek(default=default)
|
|
293
|
+
return super().peek() if is_sentinel(default) else super().peek(default=default)
|
|
296
294
|
|
|
297
295
|
def takewhile(self, predicate: Callable[[T], bool], /) -> Iterator[T]:
|
|
298
296
|
while bool(self) and predicate(self.peek()):
|
|
@@ -374,7 +372,7 @@ def _yield_splits2[T](
|
|
|
374
372
|
len_tail = max(len_win - head, 0)
|
|
375
373
|
if len_tail >= 1:
|
|
376
374
|
yield window, head, len_tail
|
|
377
|
-
case
|
|
375
|
+
case never:
|
|
378
376
|
assert_never(never)
|
|
379
377
|
|
|
380
378
|
|
utilities/operator.py
CHANGED
|
@@ -6,9 +6,9 @@ from dataclasses import asdict, dataclass
|
|
|
6
6
|
from typing import TYPE_CHECKING, Any, cast, override
|
|
7
7
|
|
|
8
8
|
import utilities.math
|
|
9
|
-
from utilities.functions import is_dataclass_instance
|
|
10
9
|
from utilities.iterables import SortIterableError, sort_iterable
|
|
11
10
|
from utilities.reprlib import get_repr
|
|
11
|
+
from utilities.typing import is_dataclass_instance
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
14
|
from utilities.types import Dataclass, Number
|