dycw-utilities 0.131.11__py3-none-any.whl → 0.131.13__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.
utilities/logging.py CHANGED
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import datetime as dt
4
3
  import re
5
4
  from dataclasses import dataclass, field
6
5
  from functools import cached_property
@@ -19,7 +18,6 @@ from logging import (
19
18
  from logging.handlers import BaseRotatingHandler, TimedRotatingFileHandler
20
19
  from pathlib import Path
21
20
  from re import Pattern
22
- from time import time
23
21
  from typing import (
24
22
  TYPE_CHECKING,
25
23
  Any,
@@ -32,17 +30,31 @@ from typing import (
32
30
  override,
33
31
  )
34
32
 
33
+ from whenever import PlainDateTime, ZonedDateTime
34
+
35
35
  from utilities.atomicwrites import move_many
36
36
  from utilities.dataclasses import replace_non_sentinel
37
- from utilities.datetime import parse_datetime_compact, serialize_compact
38
37
  from utilities.errors import ImpossibleCaseError
39
38
  from utilities.iterables import OneEmptyError, always_iterable, one
40
39
  from utilities.pathlib import ensure_suffix, get_path
40
+ from utilities.re import (
41
+ ExtractGroupError,
42
+ ExtractGroupsError,
43
+ extract_group,
44
+ extract_groups,
45
+ )
41
46
  from utilities.sentinel import Sentinel, sentinel
42
- from utilities.tzlocal import get_now_local
47
+ from utilities.tzlocal import LOCAL_TIME_ZONE_NAME
48
+ from utilities.whenever2 import (
49
+ WheneverLogRecord,
50
+ format_compact,
51
+ get_now,
52
+ get_now_local,
53
+ )
43
54
 
44
55
  if TYPE_CHECKING:
45
56
  from collections.abc import Callable, Iterable, Mapping
57
+ from datetime import time
46
58
  from logging import _FilterType
47
59
 
48
60
  from utilities.types import (
@@ -54,7 +66,9 @@ if TYPE_CHECKING:
54
66
  )
55
67
 
56
68
 
57
- _DEFAULT_FORMAT = "{asctime} | {name}:{funcName}:{lineno} | {levelname:8} | {message}"
69
+ _DEFAULT_FORMAT = (
70
+ "{zoned_datetime} | {name}:{funcName}:{lineno} | {levelname:8} | {message}"
71
+ )
58
72
  _DEFAULT_DATEFMT = "%Y-%m-%d %H:%M:%S"
59
73
  _DEFAULT_BACKUP_COUNT: int = 100
60
74
  _DEFAULT_MAX_BYTES: int = 10 * 1024**2
@@ -76,7 +90,6 @@ def add_filters(handler: Handler, /, *filters: _FilterType) -> None:
76
90
  def basic_config(
77
91
  *,
78
92
  obj: LoggerOrName | Handler | None = None,
79
- whenever: bool = False,
80
93
  format_: str = _DEFAULT_FORMAT,
81
94
  datefmt: str = _DEFAULT_DATEFMT,
82
95
  level: LogLevel = "INFO",
@@ -93,7 +106,6 @@ def basic_config(
93
106
  logger.addHandler(handler := StreamHandler())
94
107
  basic_config(
95
108
  obj=handler,
96
- whenever=whenever,
97
109
  format_=format_,
98
110
  datefmt=datefmt,
99
111
  level=level,
@@ -104,7 +116,6 @@ def basic_config(
104
116
  case str() as name:
105
117
  basic_config(
106
118
  obj=get_logger(logger=name),
107
- whenever=whenever,
108
119
  format_=format_,
109
120
  datefmt=datefmt,
110
121
  level=level,
@@ -117,7 +128,6 @@ def basic_config(
117
128
  if filters is not None:
118
129
  add_filters(handler, *always_iterable(filters))
119
130
  formatter = get_formatter(
120
- whenever=whenever,
121
131
  format_=format_,
122
132
  datefmt=datefmt,
123
133
  plain=plain,
@@ -179,18 +189,13 @@ class _FieldStyleDict(TypedDict):
179
189
 
180
190
  def get_formatter(
181
191
  *,
182
- whenever: bool = False,
183
192
  format_: str = _DEFAULT_FORMAT,
184
193
  datefmt: str = _DEFAULT_DATEFMT,
185
194
  plain: bool = False,
186
195
  color_field_styles: Mapping[str, _FieldStyleKeys] | None = None,
187
196
  ) -> Formatter:
188
197
  """Get the formatter; colored if available."""
189
- if whenever:
190
- from utilities.whenever2 import WheneverLogRecord
191
-
192
- setLogRecordFactory(WheneverLogRecord)
193
- format_ = format_.replace("{asctime}", "{zoned_datetime}")
198
+ setLogRecordFactory(WheneverLogRecord)
194
199
  if plain:
195
200
  return _get_plain_formatter(format_=format_, datefmt=datefmt)
196
201
  try:
@@ -199,8 +204,7 @@ def get_formatter(
199
204
  return _get_plain_formatter(format_=format_, datefmt=datefmt)
200
205
  default = cast("dict[_FieldStyleKeys, _FieldStyleDict]", DEFAULT_FIELD_STYLES)
201
206
  field_styles = {cast("str", k): v for k, v in default.items()}
202
- if whenever:
203
- field_styles["zoned_datetime"] = default["asctime"]
207
+ field_styles["zoned_datetime"] = default["asctime"]
204
208
  if color_field_styles is not None:
205
209
  field_styles.update({k: default[v] for k, v in color_field_styles.items()})
206
210
  return ColoredFormatter(
@@ -256,7 +260,6 @@ class GetLoggingLevelNumberError(Exception):
256
260
  def setup_logging(
257
261
  *,
258
262
  logger: LoggerOrName | None = None,
259
- whenever: bool = False,
260
263
  format_: str = _DEFAULT_FORMAT,
261
264
  datefmt: str = _DEFAULT_DATEFMT,
262
265
  console_level: LogLevel = "INFO",
@@ -272,7 +275,6 @@ def setup_logging(
272
275
  """Set up logger."""
273
276
  basic_config(
274
277
  obj=logger,
275
- whenever=whenever,
276
278
  format_=f"{console_prefix} {format_}",
277
279
  datefmt=datefmt,
278
280
  level=console_level,
@@ -295,7 +297,6 @@ def setup_logging(
295
297
  logger_use.addHandler(handler)
296
298
  basic_config(
297
299
  obj=handler,
298
- whenever=whenever,
299
300
  format_=format_,
300
301
  datefmt=datefmt,
301
302
  level=level,
@@ -330,7 +331,7 @@ class SizeAndTimeRotatingFileHandler(BaseRotatingHandler):
330
331
  interval: int = 1,
331
332
  backupCount: int = _DEFAULT_BACKUP_COUNT,
332
333
  utc: bool = False,
333
- atTime: dt.time | None = None,
334
+ atTime: time | None = None,
334
335
  ) -> None:
335
336
  path = Path(filename)
336
337
  path.parent.mkdir(parents=True, exist_ok=True)
@@ -382,7 +383,7 @@ class SizeAndTimeRotatingFileHandler(BaseRotatingHandler):
382
383
  if not self.delay: # pragma: no cover
383
384
  self.stream = self._open()
384
385
  self._time_handler.rolloverAt = ( # skipif-ci-and-windows
385
- self._time_handler.computeRollover(int(time()))
386
+ self._time_handler.computeRollover(get_now().timestamp())
386
387
  )
387
388
 
388
389
  def _should_rollover(self, record: LogRecord, /) -> bool:
@@ -400,10 +401,8 @@ class SizeAndTimeRotatingFileHandler(BaseRotatingHandler):
400
401
  def _compute_rollover_patterns(stem: str, suffix: str, /) -> _RolloverPatterns:
401
402
  return _RolloverPatterns(
402
403
  pattern1=re.compile(rf"^{stem}\.(\d+){suffix}$"),
403
- pattern2=re.compile(rf"^{stem}\.(\d+)__(\d{{8}}T\d{{6}}){suffix}$"),
404
- pattern3=re.compile(
405
- rf"^{stem}\.(\d+)__(\d{{8}}T\d{{6}})__(\d{{8}}T\d{{6}}){suffix}$"
406
- ),
404
+ pattern2=re.compile(rf"^{stem}\.(\d+)__([\dT]+?){suffix}$"),
405
+ pattern3=re.compile(rf"^{stem}\.(\d+)__([\dT]+?)__([\dT]+?){suffix}$"),
407
406
  )
408
407
 
409
408
 
@@ -452,7 +451,7 @@ def _compute_rollover_actions(
452
451
  rotations.add(
453
452
  _Rotation(file=file, index=curr + 1, start=start, end=end)
454
453
  )
455
- case int() as index, dt.datetime(), dt.datetime():
454
+ case int() as index, ZonedDateTime(), ZonedDateTime():
456
455
  rotations.add(_Rotation(file=file, index=index + 1))
457
456
  case _: # pragma: no cover
458
457
  raise NotImplementedError
@@ -476,8 +475,8 @@ class _RotatingLogFile:
476
475
  stem: str
477
476
  suffix: str
478
477
  index: int | None = None
479
- start: dt.datetime | None = None
480
- end: dt.datetime | None = None
478
+ start: ZonedDateTime | None = None
479
+ end: ZonedDateTime | None = None
481
480
 
482
481
  @classmethod
483
482
  def from_path(
@@ -494,16 +493,23 @@ class _RotatingLogFile:
494
493
  if patterns is None:
495
494
  patterns = _compute_rollover_patterns(stem, suffix)
496
495
  try:
497
- (index,) = patterns.pattern1.findall(path.name)
498
- except ValueError:
496
+ index, start, end = extract_groups(patterns.pattern3, path.name)
497
+ except ExtractGroupsError:
499
498
  pass
500
499
  else:
501
500
  return cls(
502
- directory=path.parent, stem=stem, suffix=suffix, index=int(index)
501
+ directory=path.parent,
502
+ stem=stem,
503
+ suffix=suffix,
504
+ index=int(index),
505
+ start=PlainDateTime.parse_common_iso(start).assume_tz(
506
+ LOCAL_TIME_ZONE_NAME
507
+ ),
508
+ end=PlainDateTime.parse_common_iso(end).assume_tz(LOCAL_TIME_ZONE_NAME),
503
509
  )
504
510
  try:
505
- ((index, end),) = patterns.pattern2.findall(path.name)
506
- except ValueError:
511
+ index, end = extract_groups(patterns.pattern2, path.name)
512
+ except ExtractGroupsError:
507
513
  pass
508
514
  else:
509
515
  return cls(
@@ -511,21 +517,17 @@ class _RotatingLogFile:
511
517
  stem=stem,
512
518
  suffix=suffix,
513
519
  index=int(index),
514
- end=parse_datetime_compact(end),
520
+ end=PlainDateTime.parse_common_iso(end).assume_tz(LOCAL_TIME_ZONE_NAME),
515
521
  )
516
522
  try:
517
- ((index, start, end),) = patterns.pattern3.findall(path.name)
518
- except ValueError:
519
- return cls(directory=path.parent, stem=stem, suffix=suffix)
523
+ index = extract_group(patterns.pattern1, path.name)
524
+ except ExtractGroupError:
525
+ pass
520
526
  else:
521
527
  return cls(
522
- directory=path.parent,
523
- stem=stem,
524
- suffix=suffix,
525
- index=int(index),
526
- start=parse_datetime_compact(start),
527
- end=parse_datetime_compact(end),
528
+ directory=path.parent, stem=stem, suffix=suffix, index=int(index)
528
529
  )
530
+ return cls(directory=path.parent, stem=stem, suffix=suffix)
529
531
 
530
532
  @cached_property
531
533
  def path(self) -> Path:
@@ -535,10 +537,10 @@ class _RotatingLogFile:
535
537
  tail = None
536
538
  case int() as index, None, None:
537
539
  tail = str(index)
538
- case int() as index, None, dt.datetime() as end:
539
- tail = f"{index}__{serialize_compact(end)}"
540
- case int() as index, dt.datetime() as start, dt.datetime() as end:
541
- tail = f"{index}__{serialize_compact(start)}__{serialize_compact(end)}"
540
+ case int() as index, None, ZonedDateTime() as end:
541
+ tail = f"{index}__{format_compact(end)}"
542
+ case int() as index, ZonedDateTime() as start, ZonedDateTime() as end:
543
+ tail = f"{index}__{format_compact(start)}__{format_compact(end)}"
542
544
  case _: # pragma: no cover
543
545
  raise ImpossibleCaseError(
544
546
  case=[f"{self.index=}", f"{self.start=}", f"{self.end=}"]
@@ -550,8 +552,8 @@ class _RotatingLogFile:
550
552
  self,
551
553
  *,
552
554
  index: int | None | Sentinel = sentinel,
553
- start: dt.datetime | None | Sentinel = sentinel,
554
- end: dt.datetime | None | Sentinel = sentinel,
555
+ start: ZonedDateTime | None | Sentinel = sentinel,
556
+ end: ZonedDateTime | None | Sentinel = sentinel,
555
557
  ) -> Self:
556
558
  return replace_non_sentinel(self, index=index, start=start, end=end)
557
559
 
@@ -568,8 +570,8 @@ class _Deletion:
568
570
  class _Rotation:
569
571
  file: _RotatingLogFile
570
572
  index: int = 0
571
- start: dt.datetime | None | Sentinel = sentinel
572
- end: dt.datetime | Sentinel = sentinel
573
+ start: ZonedDateTime | None | Sentinel = sentinel
574
+ end: ZonedDateTime | Sentinel = sentinel
573
575
 
574
576
  @cached_property
575
577
  def destination(self) -> Path:
utilities/orjson.py CHANGED
@@ -14,6 +14,7 @@ from pathlib import Path
14
14
  from re import Pattern, search
15
15
  from typing import TYPE_CHECKING, Any, Literal, Self, assert_never, overload, override
16
16
  from uuid import UUID
17
+ from zoneinfo import ZoneInfo
17
18
 
18
19
  from orjson import (
19
20
  OPT_PASSTHROUGH_DATACLASS,
@@ -22,6 +23,7 @@ from orjson import (
22
23
  dumps,
23
24
  loads,
24
25
  )
26
+ from whenever import ZonedDateTime
25
27
 
26
28
  from utilities.concurrent import concurrent_map
27
29
  from utilities.dataclasses import dataclass_to_dict
@@ -35,15 +37,8 @@ from utilities.iterables import (
35
37
  )
36
38
  from utilities.logging import get_logging_level_number
37
39
  from utilities.math import MAX_INT64, MIN_INT64
38
- from utilities.types import (
39
- Dataclass,
40
- DateOrDateTime,
41
- LogLevel,
42
- MaybeIterable,
43
- PathLike,
44
- StrMapping,
45
- )
46
- from utilities.tzlocal import get_local_time_zone
40
+ from utilities.types import Dataclass, LogLevel, MaybeIterable, PathLike, StrMapping
41
+ from utilities.tzlocal import LOCAL_TIME_ZONE
47
42
  from utilities.uuid import UUID_PATTERN
48
43
  from utilities.version import Version, parse_version
49
44
  from utilities.whenever import (
@@ -57,12 +52,14 @@ from utilities.whenever import (
57
52
  serialize_time,
58
53
  serialize_timedelta,
59
54
  )
60
- from utilities.zoneinfo import ensure_time_zone
55
+ from utilities.whenever2 import from_timestamp
61
56
 
62
57
  if TYPE_CHECKING:
63
58
  from collections.abc import Set as AbstractSet
64
59
  from logging import _FormatStyle
65
60
 
61
+ from whenever import Date
62
+
66
63
  from utilities.types import Parallelism
67
64
 
68
65
 
@@ -92,6 +89,7 @@ class _Prefixes(Enum):
92
89
  unserializable = "un"
93
90
  uuid = "uu"
94
91
  version = "v"
92
+ zoned_datetime = "zd"
95
93
 
96
94
 
97
95
  type _DataclassHook = Callable[[type[Dataclass], StrMapping], StrMapping]
@@ -194,6 +192,8 @@ def _pre_process(
194
192
  return f"[{_Prefixes.exception_class.value}|{error_cls.__qualname__}]"
195
193
  case Version() as version:
196
194
  return f"[{_Prefixes.version.value}]{version!s}"
195
+ case ZonedDateTime() as datetime:
196
+ return f"[{_Prefixes.zoned_datetime.value}]{datetime}"
197
197
  # contains
198
198
  case Dataclass() as dataclass:
199
199
  asdict = dataclass_to_dict(
@@ -350,6 +350,11 @@ _ZONED_DATETIME_PATTERN = re.compile(
350
350
  + _Prefixes.datetime.value
351
351
  + r"\](\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?[\+\-]\d{2}:\d{2}(?::\d{2})?\[(?!(?:dt\.)).+?\])$"
352
352
  )
353
+ _ZONED_DATETIME_PATTERN2 = re.compile(
354
+ r"^\["
355
+ + _Prefixes.zoned_datetime.value
356
+ + r"\](\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?[\+\-]\d{2}:\d{2}(?::\d{2})?\[(?!(?:dt\.)).+?\])$"
357
+ )
353
358
 
354
359
 
355
360
  def _make_unit_pattern(prefix: _Prefixes, /) -> Pattern[str]:
@@ -437,6 +442,8 @@ def _object_hook(
437
442
  return parse_version(match.group(1))
438
443
  if match := _ZONED_DATETIME_PATTERN.search(text):
439
444
  return parse_zoned_datetime(match.group(1))
445
+ if match := _ZONED_DATETIME_PATTERN2.search(text):
446
+ return ZonedDateTime.parse_common_iso(match.group(1))
440
447
  if (
441
448
  exc_class := _object_hook_exception_class(
442
449
  text, data=data, objects=objects, redirects=redirects
@@ -753,9 +760,7 @@ class OrjsonFormatter(Formatter):
753
760
  path_name=Path(record.pathname),
754
761
  line_num=record.lineno,
755
762
  message=record.getMessage(),
756
- datetime=dt.datetime.fromtimestamp(
757
- record.created, tz=get_local_time_zone()
758
- ),
763
+ datetime=from_timestamp(record.created, time_zone=LOCAL_TIME_ZONE),
759
764
  func_name=record.funcName,
760
765
  extra=extra if len(extra) >= 1 else None,
761
766
  )
@@ -859,11 +864,16 @@ class GetLogRecordsOutput:
859
864
  for r in self.records
860
865
  ]
861
866
  if len(records) >= 1:
862
- time_zone = one_unique(ensure_time_zone(r.datetime) for r in records)
867
+ time_zone = one_unique(ZoneInfo(r.datetime.tz) for r in records)
863
868
  else:
864
- time_zone = get_local_time_zone()
869
+ time_zone = LOCAL_TIME_ZONE
865
870
  return DataFrame(
866
- data=[dataclass_to_dict(r, recursive=False) for r in records],
871
+ data=[
872
+ dataclass_to_dict(
873
+ replace(r, datetime=r.datetime.py_datetime()), recursive=False
874
+ )
875
+ for r in records
876
+ ],
867
877
  schema={
868
878
  "index": UInt64,
869
879
  "name": String,
@@ -891,9 +901,12 @@ class GetLogRecordsOutput:
891
901
  level: LogLevel | None = None,
892
902
  min_level: LogLevel | None = None,
893
903
  max_level: LogLevel | None = None,
894
- date_or_datetime: DateOrDateTime | None = None,
895
- min_date_or_datetime: DateOrDateTime | None = None,
896
- max_date_or_datetime: DateOrDateTime | None = None,
904
+ date: Date | None = None,
905
+ min_date: Date | None = None,
906
+ max_date: Date | None = None,
907
+ datetime: ZonedDateTime | None = None,
908
+ min_datetime: ZonedDateTime | None = None,
909
+ max_datetime: ZonedDateTime | None = None,
897
910
  func_name: bool | str | None = None,
898
911
  extra: bool | MaybeIterable[str] | None = None,
899
912
  log_file: bool | PathLike | None = None,
@@ -932,30 +945,18 @@ class GetLogRecordsOutput:
932
945
  records = [
933
946
  r for r in records if r.level <= get_logging_level_number(max_level)
934
947
  ]
935
- if date_or_datetime is not None:
936
- match date_or_datetime:
937
- case dt.datetime() as datetime:
938
- records = [r for r in records if r.datetime == datetime]
939
- case dt.date() as date:
940
- records = [r for r in records if r.date == date]
941
- case _ as never:
942
- assert_never(never)
943
- if min_date_or_datetime is not None:
944
- match min_date_or_datetime:
945
- case dt.datetime() as min_datetime:
946
- records = [r for r in records if r.datetime >= min_datetime]
947
- case dt.date() as min_date:
948
- records = [r for r in records if r.date >= min_date]
949
- case _ as never:
950
- assert_never(never)
951
- if max_date_or_datetime is not None:
952
- match max_date_or_datetime:
953
- case dt.datetime() as max_datetime:
954
- records = [r for r in records if r.datetime <= max_datetime]
955
- case dt.date() as max_date:
956
- records = [r for r in records if r.date <= max_date]
957
- case _ as never:
958
- assert_never(never)
948
+ if date is not None:
949
+ records = [r for r in records if r.date == date]
950
+ if min_date is not None:
951
+ records = [r for r in records if r.date >= min_date]
952
+ if max_date is not None:
953
+ records = [r for r in records if r.date <= max_date]
954
+ if datetime is not None:
955
+ records = [r for r in records if r.datetime == datetime]
956
+ if min_datetime is not None:
957
+ records = [r for r in records if r.datetime >= min_datetime]
958
+ if max_datetime is not None:
959
+ records = [r for r in records if r.datetime <= max_datetime]
959
960
  if func_name is not None:
960
961
  match func_name:
961
962
  case bool() as has_func_name:
@@ -1058,7 +1059,7 @@ class OrjsonLogRecord:
1058
1059
  level: int
1059
1060
  path_name: Path
1060
1061
  line_num: int
1061
- datetime: dt.datetime
1062
+ datetime: ZonedDateTime
1062
1063
  func_name: str | None = None
1063
1064
  stack_info: str | None = None
1064
1065
  extra: StrMapping | None = None
@@ -1066,7 +1067,7 @@ class OrjsonLogRecord:
1066
1067
  log_file_line_num: int | None = None
1067
1068
 
1068
1069
  @cached_property
1069
- def date(self) -> dt.date:
1070
+ def date(self) -> Date:
1070
1071
  return self.datetime.date()
1071
1072
 
1072
1073