dycw-utilities 0.136.9__py3-none-any.whl → 0.137.0__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.
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.136.9
3
+ Version: 0.137.0
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
7
7
  Requires-Dist: atomicwrites<1.5,>=1.4.1
8
8
  Requires-Dist: typing-extensions<4.15,>=4.14.0
9
9
  Requires-Dist: tzlocal<5.4,>=5.3.1
10
- Requires-Dist: whenever<0.9,>=0.8.5
10
+ Requires-Dist: whenever<0.9,>=0.8.6
11
11
  Provides-Extra: logging
12
12
  Requires-Dist: coloredlogs<15.1,>=15.0.1; extra == 'logging'
13
13
  Provides-Extra: test
@@ -1,4 +1,4 @@
1
- utilities/__init__.py,sha256=TfRJ5LJDqhQMm2y-jbpj1oPenJALJoqsiY7i5XiMjcE,60
1
+ utilities/__init__.py,sha256=PUxpzAWl2k1kpcIxVjuaCvi36fODyVik-8mExgNWSXk,60
2
2
  utilities/aiolimiter.py,sha256=mD0wEiqMgwpty4XTbawFpnkkmJS6R4JRsVXFUaoitSU,628
3
3
  utilities/altair.py,sha256=HeZBVUocjkrTNwwKrClppsIqgNFF-ykv05HfZSoHYno,9104
4
4
  utilities/asyncio.py,sha256=dcGeKQzjLBXxKzZkVIk5oZsFXEcynVbRB9iNB5XEDZk,38526
@@ -16,7 +16,7 @@ utilities/enum.py,sha256=IEPMGiNJBcofGPuoGZErf4bMNSBE4SLs32nvm2r81h0,5791
16
16
  utilities/errors.py,sha256=nC7ZYtxxDBMfrTHtT_MByBfup_wfGQFRo3eDt-0ZPe8,1045
17
17
  utilities/eventkit.py,sha256=cV76NIHKDPyrhnZRuhjCXse5CQqgRGXZ7p7Gon4Mu2g,12646
18
18
  utilities/fastapi.py,sha256=E8T2J1-N_RbpkN4czthU6NPIxAZDzxy-k_WGJaxeJ48,2671
19
- utilities/fpdf2.py,sha256=PmPj8ugr_SlxFEpw-9OsI8--mteLQ4MaXv_Cbmf7XXs,1852
19
+ utilities/fpdf2.py,sha256=776PkEX5xEK-whFOzqaVaQVHPy1Xf01kCSyj7TEp80g,1886
20
20
  utilities/functions.py,sha256=qefAfW0zz7OEiRuBtKF-3tI3NaufcwAULRIFv24gZ2Q,28533
21
21
  utilities/functools.py,sha256=I00ru2gQPakZw2SHVeKIKXfTv741655s6HI0lUoE0D4,1552
22
22
  utilities/getpass.py,sha256=DfN5UgMAtFCqS3dSfFHUfqIMZX2shXvwphOz_6J6f6A,103
@@ -31,7 +31,7 @@ utilities/iterables.py,sha256=wlcm0PS2fKG-H0r0k367NOLrryMbfXLlwzUeAmBSvv8,43392
31
31
  utilities/jupyter.py,sha256=ft5JA7fBxXKzP-L9W8f2-wbF0QeYc_2uLQNFDVk4Z-M,2917
32
32
  utilities/libcst.py,sha256=XTT8cCYAYfI9ZIdxjiTCqbF45cN-viDNDa7GE5mR2T4,5615
33
33
  utilities/lightweight_charts.py,sha256=JrkrAZMo6JID2Eoc9QCc05Y_pK4l2zsApIhmii1z2Ig,2764
34
- utilities/logging.py,sha256=j0xS7bNdZcMAobWSRahpg_d7GWewd_99oXvexrjWm6k,17841
34
+ utilities/logging.py,sha256=O2IApkDJQ1EqZHCvxp0YHrML7a8UuORtb66Hou_vPfw,17923
35
35
  utilities/luigi.py,sha256=wK7cB3y8NXeSa8d6r_yTKRmjMguNmIPmy52yg10vPaI,4774
36
36
  utilities/math.py,sha256=_6vrDyjtaqE_OFE-F2DNWrDG_J_kMl3nFAJsok9v_bY,26862
37
37
  utilities/memory_profiler.py,sha256=XzN56jDCa5aqXS_DxEjb_K4L6aIWh_5zyKi6OhcIxw0,853
@@ -44,7 +44,7 @@ utilities/orjson.py,sha256=y5ynSGhQjX7vikfvsHh_AklLnH7gjvsSkIC3h5tnx98,36474
44
44
  utilities/os.py,sha256=yMNAKMyY8oFgQ1yN3TQYnwa5-A_FXz4tCDbhIctQHSs,3736
45
45
  utilities/parse.py,sha256=bCZW1bBfXM2j-yLGq0TGUshOnbL8V-U8amGu_OZTE_I,17907
46
46
  utilities/pathlib.py,sha256=jCFPZm4rBKylEva9wDVTwQlTTVKMepu92WrTpoGa438,3248
47
- utilities/period.py,sha256=QZsy2tCcoh0LGr70bkvlB5iWqEx6hdit3-iuaWJYXXc,4689
47
+ utilities/period.py,sha256=6jEff_qAiE7xdFaQ1DnKgNf10D2wHhzt7hQXCBoKlgc,6842
48
48
  utilities/pickle.py,sha256=MBT2xZCsv0pH868IXLGKnlcqNx2IRVKYNpRcqiQQqxw,653
49
49
  utilities/platform.py,sha256=5uCKRf_ij7ukJDcbnNfhY2ay9fbrpiNLRO1t2QvcwqQ,2825
50
50
  utilities/polars.py,sha256=xCeB-pLkezOoQJvgxTdz2rNlaehdhek-HmDztXWn2j0,63266
@@ -54,7 +54,7 @@ utilities/pqdm.py,sha256=BTsYPtbKQWwX-iXF4qCkfPG7DPxIB54J989n83bXrIo,3092
54
54
  utilities/psutil.py,sha256=0j4YxtVb8VjaaKKiHg6UEK95SUPkEcENgPtLgPJsNv0,3760
55
55
  utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
56
  utilities/pydantic.py,sha256=CmxCi4sukeHM3JGjJ1Rbp8UAvcx4MZapLg10mFYJ-nk,1771
57
- utilities/pyinstrument.py,sha256=_Rfq6Gg4NKV2NF0dRYOpK2IRyyePxI7-3UmHIQLYrlQ,852
57
+ utilities/pyinstrument.py,sha256=HrTGJ2niUAHUFMSN3im9BeedC0faq2DqoFccDxPpsP8,884
58
58
  utilities/pytest.py,sha256=vv5ZpePZS6pBxl5jY3XQR5SnAhgd4f0sSMyRsvHWg60,8086
59
59
  utilities/pytest_regressions.py,sha256=YI55B7EtLjhz7zPJZ6NK9bWrxrKCKabWZJe1cwcbA5o,5082
60
60
  utilities/python_dotenv.py,sha256=dYooRYwqrvhSoZWuiVbCiKUWiS-M5b5yv2zDWGYPEvI,3209
@@ -76,7 +76,7 @@ utilities/tempfile.py,sha256=VqmZJAhTJ1OaVywFzk5eqROV8iJbW9XQ_QYAV0bpdRo,1384
76
76
  utilities/text.py,sha256=ymBFlP_cA8OgNnZRVNs7FAh7OG8HxE6YkiLEMZv5g_A,11297
77
77
  utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
78
78
  utilities/timer.py,sha256=oXfTii6ymu57niP0BDGZjFD55LEHi2a19kqZKiTgaFQ,2588
79
- utilities/traceback.py,sha256=YvNUQCTDLvrKYAsl5498a0zhfjJna0dEUvIaK7PmLlQ,8780
79
+ utilities/traceback.py,sha256=h9yt4C2XkGHXW9e9bz93IGluhIR_Qil8zu5dhC8DXY0,8884
80
80
  utilities/typed_settings.py,sha256=cZqA2IjG1erF1SUZFSsjIHmZX7BP5iIfPd6CixHllA0,4367
81
81
  utilities/types.py,sha256=98pwEfkBikg6UUrshNJZgc8l97FGBbi0twIsqVt-KNA,17247
82
82
  utilities/typing.py,sha256=Z-_XDaWyT_6wIo3qfNK-hvRlzxP2Jxa9PgXzm5rDYRA,13790
@@ -85,10 +85,10 @@ utilities/tzlocal.py,sha256=KyCXEgCTjqGFx-389JdTuhMRUaT06U1RCMdWoED-qro,728
85
85
  utilities/uuid.py,sha256=32p7DGHGM2Btx6PcBvCZvERSWbpupMXqx6FppPoSoTU,612
86
86
  utilities/version.py,sha256=ufhJMmI6KPs1-3wBI71aj5wCukd3sP_m11usLe88DNA,5117
87
87
  utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
88
- utilities/whenever.py,sha256=A-yoOqBqrcVD1yDINDsTFDw7dq9-zgUGn_f8CxVUQJs,23332
88
+ utilities/whenever.py,sha256=HdkS19yRIRfevH3kkDw1SJVVZbK3hgAXeb6ZFoUjh38,24094
89
89
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
90
90
  utilities/zoneinfo.py,sha256=oEH-nL3t4h9uawyZqWDtNtDAl6M-CLpLYGI_nI6DulM,1971
91
- dycw_utilities-0.136.9.dist-info/METADATA,sha256=MOcxHEd2C4kHuMPGAw3BKehTyJMq0qejj7XF1I3KYYc,1637
92
- dycw_utilities-0.136.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
93
- dycw_utilities-0.136.9.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
94
- dycw_utilities-0.136.9.dist-info/RECORD,,
91
+ dycw_utilities-0.137.0.dist-info/METADATA,sha256=S6dRJeuw2HB30BzEpinRuxWt8SORnzlVVs62Dk_0vD4,1637
92
+ dycw_utilities-0.137.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
93
+ dycw_utilities-0.137.0.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
94
+ dycw_utilities-0.137.0.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.136.9"
3
+ __version__ = "0.137.0"
utilities/fpdf2.py CHANGED
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, override
6
6
  from fpdf import FPDF
7
7
  from fpdf.enums import XPos, YPos
8
8
 
9
- from utilities.whenever import format_compact, get_now
9
+ from utilities.whenever import format_compact, get_now, to_local_plain
10
10
 
11
11
  if TYPE_CHECKING:
12
12
  from collections.abc import Iterator
@@ -47,7 +47,7 @@ def yield_pdf(*, header: str | None = None) -> Iterator[_BasePDF]:
47
47
  def footer(self) -> None:
48
48
  self.set_y(-15)
49
49
  self.set_font(family="Helvetica", style="I", size=8)
50
- page_no, now = self.page_no(), format_compact(get_now())
50
+ page_no, now = (self.page_no(), format_compact(to_local_plain(get_now())))
51
51
  text = f"page {page_no}/{{}}; {now}"
52
52
  _ = self.cell(
53
53
  w=0,
utilities/logging.py CHANGED
@@ -45,7 +45,12 @@ from utilities.re import (
45
45
  )
46
46
  from utilities.sentinel import Sentinel, sentinel
47
47
  from utilities.tzlocal import LOCAL_TIME_ZONE_NAME
48
- from utilities.whenever import WheneverLogRecord, format_compact, get_now, get_now_local
48
+ from utilities.whenever import (
49
+ WheneverLogRecord,
50
+ format_compact,
51
+ get_now_local,
52
+ to_local_plain,
53
+ )
49
54
 
50
55
  if TYPE_CHECKING:
51
56
  from collections.abc import Callable, Iterable, Mapping
@@ -378,7 +383,7 @@ class SizeAndTimeRotatingFileHandler(BaseRotatingHandler):
378
383
  if not self.delay: # pragma: no cover
379
384
  self.stream = self._open()
380
385
  self._time_handler.rolloverAt = ( # skipif-ci-and-windows
381
- self._time_handler.computeRollover(get_now().timestamp())
386
+ self._time_handler.computeRollover(get_now_local().timestamp())
382
387
  )
383
388
 
384
389
  def _should_rollover(self, record: LogRecord, /) -> bool:
@@ -533,9 +538,9 @@ class _RotatingLogFile:
533
538
  case int() as index, None, None:
534
539
  tail = str(index)
535
540
  case int() as index, None, ZonedDateTime() as end:
536
- tail = f"{index}__{format_compact(end)}"
541
+ tail = f"{index}__{format_compact(to_local_plain(end))}"
537
542
  case int() as index, ZonedDateTime() as start, ZonedDateTime() as end:
538
- tail = f"{index}__{format_compact(start)}__{format_compact(end)}"
543
+ tail = f"{index}__{format_compact(to_local_plain(start))}__{format_compact(to_local_plain(end))}"
539
544
  case _: # pragma: no cover
540
545
  raise ImpossibleCaseError(
541
546
  case=[f"{self.index=}", f"{self.start=}", f"{self.end=}"]
utilities/period.py CHANGED
@@ -9,6 +9,7 @@ from whenever import Date, DateDelta, TimeDelta, ZonedDateTime
9
9
  from utilities.dataclasses import replace_non_sentinel
10
10
  from utilities.functions import get_class_name
11
11
  from utilities.sentinel import Sentinel, sentinel
12
+ from utilities.whenever import format_compact
12
13
  from utilities.zoneinfo import get_time_zone_name
13
14
 
14
15
  if TYPE_CHECKING:
@@ -53,6 +54,17 @@ class DatePeriod:
53
54
  """The delta of the period."""
54
55
  return self.end - self.start
55
56
 
57
+ def format_compact(self) -> str:
58
+ """Format the period in a compact fashion."""
59
+ fc, start, end = format_compact, self.start, self.end
60
+ if self.start == self.end:
61
+ return f"{fc(start)}="
62
+ if self.start.year_month() == self.end.year_month():
63
+ return f"{fc(start)}-{fc(end, fmt='%d')}"
64
+ if self.start.year == self.end.year:
65
+ return f"{fc(start)}-{fc(end, fmt='%m%d')}"
66
+ return f"{fc(start)}-{fc(end)}"
67
+
56
68
  def replace(
57
69
  self, *, start: Date | Sentinel = sentinel, end: Date | Sentinel = sentinel
58
70
  ) -> Self:
@@ -101,6 +113,39 @@ class ZonedDateTimePeriod:
101
113
  """The duration of the period."""
102
114
  return self.end - self.start
103
115
 
116
+ def format_compact(self) -> str:
117
+ """Format the period in a compact fashion."""
118
+ fc, start, end = format_compact, self.start, self.end
119
+ if start == end:
120
+ if end.second != 0:
121
+ return f"{fc(start)}="
122
+ if end.minute != 0:
123
+ return f"{fc(start, fmt='%Y%m%dT%H%M')}="
124
+ return f"{fc(start, fmt='%Y%m%dT%H')}="
125
+ if start.date() == end.date():
126
+ if end.second != 0:
127
+ return f"{fc(start.to_plain())}-{fc(end, fmt='%H%M%S')}"
128
+ if end.minute != 0:
129
+ return f"{fc(start.to_plain())}-{fc(end, fmt='%H%M')}"
130
+ return f"{fc(start.to_plain())}-{fc(end, fmt='%H')}"
131
+ if start.date().year_month() == end.date().year_month():
132
+ if end.second != 0:
133
+ return f"{fc(start.to_plain())}-{fc(end, fmt='%dT%H%M%S')}"
134
+ if end.minute != 0:
135
+ return f"{fc(start.to_plain())}-{fc(end, fmt='%dT%H%M')}"
136
+ return f"{fc(start.to_plain())}-{fc(end, fmt='%dT%H')}"
137
+ if start.year == end.year:
138
+ if end.second != 0:
139
+ return f"{fc(start.to_plain())}-{fc(end, fmt='%m%dT%H%M%S')}"
140
+ if end.minute != 0:
141
+ return f"{fc(start.to_plain())}-{fc(end, fmt='%m%dT%H%M')}"
142
+ return f"{fc(start.to_plain())}-{fc(end, fmt='%m%dT%H')}"
143
+ if end.second != 0:
144
+ return f"{fc(start.to_plain())}-{fc(end)}"
145
+ if end.minute != 0:
146
+ return f"{fc(start.to_plain())}-{fc(end, fmt='%Y%m%dT%H%M')}"
147
+ return f"{fc(start.to_plain())}-{fc(end, fmt='%Y%m%dT%H')}"
148
+
104
149
  def replace(
105
150
  self,
106
151
  *,
utilities/pyinstrument.py CHANGED
@@ -8,7 +8,7 @@ from pyinstrument.profiler import Profiler
8
8
 
9
9
  from utilities.atomicwrites import writer
10
10
  from utilities.pathlib import get_path
11
- from utilities.whenever import format_compact, get_now
11
+ from utilities.whenever import format_compact, get_now, to_local_plain
12
12
 
13
13
  if TYPE_CHECKING:
14
14
  from collections.abc import Iterator
@@ -22,7 +22,7 @@ def profile(*, path: MaybeCallablePathLike | None = Path.cwd) -> Iterator[None]:
22
22
  with Profiler() as profiler:
23
23
  yield
24
24
  filename = get_path(path=path).joinpath(
25
- f"profile__{format_compact(get_now())}.html"
25
+ f"profile__{format_compact(to_local_plain(get_now()))}.html"
26
26
  )
27
27
  with writer(filename) as temp, temp.open(mode="w") as fh:
28
28
  _ = fh.write(profiler.output_html())
utilities/traceback.py CHANGED
@@ -27,7 +27,13 @@ from utilities.reprlib import (
27
27
  yield_mapping_repr,
28
28
  )
29
29
  from utilities.version import get_version
30
- from utilities.whenever import format_compact, get_now, to_zoned_date_time
30
+ from utilities.whenever import (
31
+ format_compact,
32
+ get_now,
33
+ get_now_local,
34
+ to_local_plain,
35
+ to_zoned_date_time,
36
+ )
31
37
 
32
38
  if TYPE_CHECKING:
33
39
  from collections.abc import Callable, Iterator, Sequence
@@ -89,7 +95,7 @@ def _yield_header_lines(
89
95
  version: MaybeCallableVersionLike | None = None,
90
96
  ) -> Iterator[str]:
91
97
  """Yield the header lines."""
92
- now = get_now()
98
+ now = get_now_local()
93
99
  start_use = to_zoned_date_time(date_time=start)
94
100
  yield f"Date/time | {format_compact(now)}"
95
101
  start_str = "" if start_use is None else format_compact(start_use)
@@ -250,7 +256,9 @@ def _make_except_hook_inner(
250
256
  _ = sys.stderr.write(f"{slim}\n") # don't 'from sys import stderr'
251
257
  if path is not None:
252
258
  path = (
253
- get_path(path=path).joinpath(format_compact(get_now())).with_suffix(".txt")
259
+ get_path(path=path)
260
+ .joinpath(format_compact(to_local_plain(get_now())))
261
+ .with_suffix(".txt")
254
262
  )
255
263
  full = format_exception_stack(
256
264
  exc_val,
utilities/whenever.py CHANGED
@@ -23,6 +23,7 @@ from whenever import (
23
23
  DateDelta,
24
24
  DateTimeDelta,
25
25
  PlainDateTime,
26
+ Time,
26
27
  TimeDelta,
27
28
  ZonedDateTime,
28
29
  )
@@ -160,10 +161,25 @@ def datetime_utc(
160
161
  ##
161
162
 
162
163
 
163
- def format_compact(datetime: ZonedDateTime, /) -> str:
164
- """Convert a zoned datetime to the local time zone, then format."""
165
- py_datetime = datetime.round().to_tz(LOCAL_TIME_ZONE_NAME).to_plain().py_datetime()
166
- return py_datetime.strftime(get_strftime("%Y%m%dT%H%M%S"))
164
+ def format_compact(
165
+ obj: Date | Time | PlainDateTime | ZonedDateTime, /, *, fmt: str | None = None
166
+ ) -> str:
167
+ """Format the date/datetime in a compact fashion."""
168
+ match obj:
169
+ case Date() as date:
170
+ obj_use = date.py_date()
171
+ fmt_use = "%Y%m%d" if fmt is None else fmt
172
+ case Time() as time:
173
+ obj_use = time.py_time()
174
+ fmt_use = "%H%M%S" if fmt is None else fmt
175
+ case PlainDateTime() as datetime:
176
+ obj_use = datetime.round().py_datetime()
177
+ fmt_use = "%Y%m%dT%H%M%S" if fmt is None else fmt
178
+ case ZonedDateTime() as datetime:
179
+ return f"{format_compact(datetime.to_plain(), fmt=fmt)}[{datetime.tz}]"
180
+ case _ as never:
181
+ assert_never(never)
182
+ return obj_use.strftime(get_strftime(fmt_use))
167
183
 
168
184
 
169
185
  ##
@@ -652,6 +668,14 @@ def to_date_time_delta(nanos: int, /) -> DateTimeDelta:
652
668
  ##
653
669
 
654
670
 
671
+ def to_local_plain(date_time: ZonedDateTime, /) -> PlainDateTime:
672
+ """Convert a datetime to its local/plain variant."""
673
+ return date_time.to_tz(LOCAL_TIME_ZONE_NAME).to_plain()
674
+
675
+
676
+ ##
677
+
678
+
655
679
  def to_nanos(delta: DateTimeDelta, /) -> int:
656
680
  """Compute the number of nanoseconds in a date-time delta."""
657
681
  months, days, _, _ = delta.in_months_days_secs_nanos()
@@ -885,6 +909,7 @@ __all__ = [
885
909
  "to_date",
886
910
  "to_date_time_delta",
887
911
  "to_days",
912
+ "to_local_plain",
888
913
  "to_nanos",
889
914
  "to_zoned_date_time",
890
915
  ]