dycw-utilities 0.129.7__py3-none-any.whl → 0.129.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.129.7.dist-info → dycw_utilities-0.129.8.dist-info}/METADATA +1 -2
- {dycw_utilities-0.129.7.dist-info → dycw_utilities-0.129.8.dist-info}/RECORD +7 -7
- utilities/__init__.py +1 -1
- utilities/logging.py +46 -18
- utilities/whenever.py +64 -1
- {dycw_utilities-0.129.7.dist-info → dycw_utilities-0.129.8.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.129.7.dist-info → dycw_utilities-0.129.8.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: dycw-utilities
|
3
|
-
Version: 0.129.
|
3
|
+
Version: 0.129.8
|
4
4
|
Author-email: Derek Wan <d.wan@icloud.com>
|
5
5
|
License-File: LICENSE
|
6
6
|
Requires-Python: >=3.12
|
@@ -98,7 +98,6 @@ Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-jupyter'
|
|
98
98
|
Provides-Extra: zzz-test-logging
|
99
99
|
Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-logging'
|
100
100
|
Requires-Dist: coloredlogs<15.1,>=15.0.1; extra == 'zzz-test-logging'
|
101
|
-
Requires-Dist: concurrent-log-handler<0.10,>=0.9.26; extra == 'zzz-test-logging'
|
102
101
|
Requires-Dist: rich<14.1,>=14.0.0; extra == 'zzz-test-logging'
|
103
102
|
Requires-Dist: tomlkit<0.14,>=0.13.2; extra == 'zzz-test-logging'
|
104
103
|
Requires-Dist: tzlocal<5.4,>=5.3.1; extra == 'zzz-test-logging'
|
@@ -1,4 +1,4 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=nud8TbHJzm02w0ycVV5vCV-F8CpfmCs3mB9e6p4DrdQ,60
|
2
2
|
utilities/altair.py,sha256=Gpja-flOo-Db0PIPJLJsgzAlXWoKUjPU1qY-DQ829ek,9156
|
3
3
|
utilities/asyncio.py,sha256=3n5EIcSq2xtEF1i4oR0oY2JmBq3NyugeHKFK39Mt22s,37987
|
4
4
|
utilities/atomicwrites.py,sha256=geFjn9Pwn-tTrtoGjDDxWli9NqbYfy3gGL6ZBctiqSo,5393
|
@@ -29,7 +29,7 @@ utilities/iterables.py,sha256=mDqw2_0MUVp-P8FklgcaVTi2TXduH0MxbhTDzzhSBho,44915
|
|
29
29
|
utilities/jupyter.py,sha256=ft5JA7fBxXKzP-L9W8f2-wbF0QeYc_2uLQNFDVk4Z-M,2917
|
30
30
|
utilities/libcst.py,sha256=Jto5ppzRzsxn4AD32IS8n0lbgLYXwsVJB6EY8giNZyY,4974
|
31
31
|
utilities/lightweight_charts.py,sha256=0xNfcsrgFI0R9xL25LtSm-W5yhfBI93qQNT6HyaXAhg,2769
|
32
|
-
utilities/logging.py,sha256=
|
32
|
+
utilities/logging.py,sha256=dA54i2gmULBLuEJ2roGYWt9pW2NvNBmx0YxlMns347M,26126
|
33
33
|
utilities/loguru.py,sha256=MEMQVWrdECxk1e3FxGzmOf21vWT9j8CAir98SEXFKPA,3809
|
34
34
|
utilities/luigi.py,sha256=fpH9MbxJDuo6-k9iCXRayFRtiVbUtibCJKugf7ygpv0,5988
|
35
35
|
utilities/math.py,sha256=-mQgbah-dPJwOEWf3SonrFoVZ2AVxMgpeQ3dfVa-oJA,26764
|
@@ -86,10 +86,10 @@ utilities/tzlocal.py,sha256=3upDNFBvGh1l9njmLR2z2S6K6VxQSb7QizYGUbAH3JU,960
|
|
86
86
|
utilities/uuid.py,sha256=jJTFxz-CWgltqNuzmythB7iEQ-Q1mCwPevUfKthZT3c,611
|
87
87
|
utilities/version.py,sha256=ufhJMmI6KPs1-3wBI71aj5wCukd3sP_m11usLe88DNA,5117
|
88
88
|
utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
|
89
|
-
utilities/whenever.py,sha256=
|
89
|
+
utilities/whenever.py,sha256=QbXgFAPuUL7PCp2hajmIP-FFIfIR1J6Y0TxJbeoj60I,18434
|
90
90
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
91
91
|
utilities/zoneinfo.py,sha256=-5j7IQ9nb7gR43rdgA7ms05im-XuqhAk9EJnQBXxCoQ,1874
|
92
|
-
dycw_utilities-0.129.
|
93
|
-
dycw_utilities-0.129.
|
94
|
-
dycw_utilities-0.129.
|
95
|
-
dycw_utilities-0.129.
|
92
|
+
dycw_utilities-0.129.8.dist-info/METADATA,sha256=JaBYjbXepIOBDjYciDSC1_bSCE8VlzxhpOPAs22-nMI,12723
|
93
|
+
dycw_utilities-0.129.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
94
|
+
dycw_utilities-0.129.8.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
95
|
+
dycw_utilities-0.129.8.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/logging.py
CHANGED
@@ -107,8 +107,9 @@ class SizeAndTimeRotatingFileHandler(BaseRotatingHandler):
|
|
107
107
|
utc: bool = False,
|
108
108
|
atTime: dt.time | None = None,
|
109
109
|
) -> None:
|
110
|
-
|
111
|
-
|
110
|
+
path = Path(filename)
|
111
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
112
|
+
super().__init__(path, mode, encoding=encoding, delay=delay, errors=errors)
|
112
113
|
self._max_bytes = maxBytes if maxBytes >= 1 else None
|
113
114
|
self._backup_count = backupCount if backupCount >= 1 else None
|
114
115
|
self._filename = Path(self.baseFilename)
|
@@ -117,7 +118,7 @@ class SizeAndTimeRotatingFileHandler(BaseRotatingHandler):
|
|
117
118
|
self._suffix = self._filename.suffix
|
118
119
|
self._patterns = _compute_rollover_patterns(self._stem, self._suffix)
|
119
120
|
self._time_handler = TimedRotatingFileHandler(
|
120
|
-
|
121
|
+
path,
|
121
122
|
when=when,
|
122
123
|
interval=interval,
|
123
124
|
backupCount=backupCount,
|
@@ -415,26 +416,53 @@ def add_filters(handler: Handler, /, *filters: _FilterType) -> None:
|
|
415
416
|
|
416
417
|
def basic_config(
|
417
418
|
*,
|
418
|
-
|
419
|
+
obj: LoggerOrName | Handler | None = None,
|
419
420
|
format_: str = "{asctime} | {name} | {levelname:8} | {message}",
|
421
|
+
whenever: bool = False,
|
420
422
|
level: LogLevel = "INFO",
|
423
|
+
plain: bool = False,
|
421
424
|
) -> None:
|
422
425
|
"""Do the basic config."""
|
426
|
+
if whenever:
|
427
|
+
format_ = format_.replace("{asctime}", "{zoned_datetime}")
|
423
428
|
datefmt = maybe_sub_pct_y("%Y-%m-%d %H:%M:%S")
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
429
|
+
match obj:
|
430
|
+
case None:
|
431
|
+
basicConfig(format=format_, datefmt=datefmt, style="{", level=level)
|
432
|
+
case Logger() as logger:
|
433
|
+
logger.setLevel(level)
|
434
|
+
logger.addHandler(handler := StreamHandler())
|
435
|
+
basic_config(
|
436
|
+
obj=handler,
|
437
|
+
format_=format_,
|
438
|
+
whenever=whenever,
|
439
|
+
level=level,
|
440
|
+
plain=plain,
|
441
|
+
)
|
442
|
+
case str() as name:
|
443
|
+
basic_config(
|
444
|
+
obj=get_logger(logger=name),
|
445
|
+
format_=format_,
|
446
|
+
whenever=whenever,
|
447
|
+
level=level,
|
448
|
+
plain=plain,
|
449
|
+
)
|
450
|
+
case Handler() as handler:
|
451
|
+
handler.setLevel(level)
|
452
|
+
if plain:
|
453
|
+
formatter = Formatter(fmt=format_, datefmt=datefmt, style="{")
|
454
|
+
else:
|
455
|
+
try:
|
456
|
+
from coloredlogs import ColoredFormatter
|
457
|
+
except ModuleNotFoundError: # pragma: no cover
|
458
|
+
formatter = Formatter(fmt=format_, datefmt=datefmt, style="{")
|
459
|
+
else:
|
460
|
+
formatter = ColoredFormatter(
|
461
|
+
fmt=format_, datefmt=datefmt, style="{"
|
462
|
+
)
|
463
|
+
handler.setFormatter(formatter)
|
464
|
+
case _ as never:
|
465
|
+
assert_never(never)
|
438
466
|
|
439
467
|
|
440
468
|
##
|
utilities/whenever.py
CHANGED
@@ -4,7 +4,9 @@ import datetime as dt
|
|
4
4
|
import re
|
5
5
|
from contextlib import suppress
|
6
6
|
from dataclasses import dataclass
|
7
|
-
from
|
7
|
+
from functools import cache
|
8
|
+
from logging import LogRecord
|
9
|
+
from typing import TYPE_CHECKING, Any, override
|
8
10
|
|
9
11
|
from whenever import (
|
10
12
|
Date,
|
@@ -33,6 +35,8 @@ from utilities.re import (
|
|
33
35
|
from utilities.zoneinfo import UTC, ensure_time_zone, get_time_zone_name
|
34
36
|
|
35
37
|
if TYPE_CHECKING:
|
38
|
+
from zoneinfo import ZoneInfo
|
39
|
+
|
36
40
|
from utilities.types import (
|
37
41
|
DateLike,
|
38
42
|
DateTimeLike,
|
@@ -561,6 +565,64 @@ class SerializeZonedDateTimeError(Exception):
|
|
561
565
|
##
|
562
566
|
|
563
567
|
|
568
|
+
class WheneverLogRecord(LogRecord):
|
569
|
+
"""Log record powered by `whenever`."""
|
570
|
+
|
571
|
+
zoned_datetime: str
|
572
|
+
|
573
|
+
@override
|
574
|
+
def __init__(
|
575
|
+
self,
|
576
|
+
name: str,
|
577
|
+
level: int,
|
578
|
+
pathname: str,
|
579
|
+
lineno: int,
|
580
|
+
msg: object,
|
581
|
+
args: Any,
|
582
|
+
exc_info: Any,
|
583
|
+
func: str | None = None,
|
584
|
+
sinfo: str | None = None,
|
585
|
+
) -> None:
|
586
|
+
super().__init__(
|
587
|
+
name, level, pathname, lineno, msg, args, exc_info, func, sinfo
|
588
|
+
)
|
589
|
+
length = self._get_length()
|
590
|
+
plain = format(self._get_now().to_plain().format_common_iso(), f"{length}s")
|
591
|
+
time_zone = self._get_time_zone_key()
|
592
|
+
self.zoned_datetime = f"{plain}[{time_zone}]"
|
593
|
+
|
594
|
+
@classmethod
|
595
|
+
@cache
|
596
|
+
def _get_time_zone(cls) -> ZoneInfo:
|
597
|
+
"""Get the local timezone."""
|
598
|
+
try:
|
599
|
+
from utilities.tzlocal import get_local_time_zone
|
600
|
+
except ModuleNotFoundError: # pragma: no cover
|
601
|
+
return UTC
|
602
|
+
return get_local_time_zone()
|
603
|
+
|
604
|
+
@classmethod
|
605
|
+
@cache
|
606
|
+
def _get_time_zone_key(cls) -> str:
|
607
|
+
"""Get the local timezone as a string."""
|
608
|
+
return cls._get_time_zone().key
|
609
|
+
|
610
|
+
@classmethod
|
611
|
+
@cache
|
612
|
+
def _get_length(cls) -> int:
|
613
|
+
"""Get maximum length of a formatted string."""
|
614
|
+
now = cls._get_now().replace(nanosecond=1000).to_plain()
|
615
|
+
return len(now.format_common_iso())
|
616
|
+
|
617
|
+
@classmethod
|
618
|
+
def _get_now(cls) -> ZonedDateTime:
|
619
|
+
"""Get the current zoned datetime."""
|
620
|
+
return ZonedDateTime.now(cls._get_time_zone().key)
|
621
|
+
|
622
|
+
|
623
|
+
##
|
624
|
+
|
625
|
+
|
564
626
|
def _to_datetime_delta(timedelta: dt.timedelta, /) -> DateTimeDelta:
|
565
627
|
"""Serialize a timedelta."""
|
566
628
|
total_microseconds = datetime_duration_to_microseconds(timedelta)
|
@@ -610,6 +672,7 @@ __all__ = [
|
|
610
672
|
"SerializePlainDateTimeError",
|
611
673
|
"SerializeTimeDeltaError",
|
612
674
|
"SerializeZonedDateTimeError",
|
675
|
+
"WheneverLogRecord",
|
613
676
|
"check_valid_zoned_datetime",
|
614
677
|
"ensure_date",
|
615
678
|
"ensure_datetime",
|
File without changes
|
File without changes
|