dycw-utilities 0.116.5__py3-none-any.whl → 0.117.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.
- {dycw_utilities-0.116.5.dist-info → dycw_utilities-0.117.0.dist-info}/METADATA +3 -3
- {dycw_utilities-0.116.5.dist-info → dycw_utilities-0.117.0.dist-info}/RECORD +8 -8
- utilities/__init__.py +1 -1
- utilities/asyncio.py +44 -1
- utilities/datetime.py +58 -55
- utilities/whenever.py +2 -2
- {dycw_utilities-0.116.5.dist-info → dycw_utilities-0.117.0.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.116.5.dist-info → dycw_utilities-0.117.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,12 +1,12 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: dycw-utilities
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.117.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: typing-extensions<4.14,>=4.13.1
|
8
8
|
Provides-Extra: test
|
9
|
-
Requires-Dist: hypothesis<6.132,>=6.131.
|
9
|
+
Requires-Dist: hypothesis<6.132,>=6.131.19; extra == 'test'
|
10
10
|
Requires-Dist: pytest-asyncio<0.27,>=0.26.0; extra == 'test'
|
11
11
|
Requires-Dist: pytest-cov<6.2,>=6.1.1; extra == 'test'
|
12
12
|
Requires-Dist: pytest-instafail<0.6,>=0.5.0; extra == 'test'
|
@@ -80,7 +80,7 @@ Provides-Extra: zzz-test-hypothesis
|
|
80
80
|
Requires-Dist: aiosqlite<0.22,>=0.21.0; extra == 'zzz-test-hypothesis'
|
81
81
|
Requires-Dist: asyncpg<0.31,>=0.30.0; extra == 'zzz-test-hypothesis'
|
82
82
|
Requires-Dist: greenlet<3.3,>=3.2.0; extra == 'zzz-test-hypothesis'
|
83
|
-
Requires-Dist: hypothesis<6.132,>=6.131.
|
83
|
+
Requires-Dist: hypothesis<6.132,>=6.131.19; extra == 'zzz-test-hypothesis'
|
84
84
|
Requires-Dist: luigi<3.7,>=3.6.0; extra == 'zzz-test-hypothesis'
|
85
85
|
Requires-Dist: numpy<2.3,>=2.2.5; extra == 'zzz-test-hypothesis'
|
86
86
|
Requires-Dist: pathvalidate<3.3,>=3.2.3; extra == 'zzz-test-hypothesis'
|
@@ -1,7 +1,7 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=P9EtLQQfrOCDM1TAOhodQ4gfhNisheGhmugAZrQJYYc,60
|
2
2
|
utilities/altair.py,sha256=Gpja-flOo-Db0PIPJLJsgzAlXWoKUjPU1qY-DQ829ek,9156
|
3
3
|
utilities/astor.py,sha256=xuDUkjq0-b6fhtwjhbnebzbqQZAjMSHR1IIS5uOodVg,777
|
4
|
-
utilities/asyncio.py,sha256=
|
4
|
+
utilities/asyncio.py,sha256=HqPgdti3ZJPH7uHJkvmZ2weIVKYEpB6FKh6FBriMAPU,24287
|
5
5
|
utilities/atomicwrites.py,sha256=geFjn9Pwn-tTrtoGjDDxWli9NqbYfy3gGL6ZBctiqSo,5393
|
6
6
|
utilities/atools.py,sha256=IYMuFSFGSKyuQmqD6v5IUtDlz8PPw0Sr87Cub_gRU3M,1168
|
7
7
|
utilities/cachetools.py,sha256=C1zqOg7BYz0IfQFK8e3qaDDgEZxDpo47F15RTfJM37Q,2910
|
@@ -12,7 +12,7 @@ utilities/contextvars.py,sha256=RsSGGrbQqqZ67rOydnM7WWIsM2lIE31UHJLejnHJPWY,505
|
|
12
12
|
utilities/cryptography.py,sha256=_CiK_K6c_-uQuUhsUNjNjTL-nqxAh4_1zTfS11Xe120,972
|
13
13
|
utilities/cvxpy.py,sha256=Rv1-fD-XYerosCavRF8Pohop2DBkU3AlFaGTfD8AEAA,13776
|
14
14
|
utilities/dataclasses.py,sha256=iiC1wpGXWhaocIikzwBt8bbLWyImoUlOlcDZJGejaIg,33011
|
15
|
-
utilities/datetime.py,sha256=
|
15
|
+
utilities/datetime.py,sha256=VOwjPibw63Myv-CRYhT2eEHpz277GqUiEDEaI7p-nQw,38985
|
16
16
|
utilities/enum.py,sha256=HoRwVCWzsnH0vpO9ZEcAAIZLMv0Sn2vJxxA4sYMQgDs,5793
|
17
17
|
utilities/errors.py,sha256=gxsaa7eq7jbYl41Of40-ivjXqJB5gt4QAcJ0smZZMJE,829
|
18
18
|
utilities/eventkit.py,sha256=6M5Xu1SzN-juk9PqBHwy5dS-ta7T0qA6SMpDsakOJ0E,13039
|
@@ -85,10 +85,10 @@ utilities/tzlocal.py,sha256=3upDNFBvGh1l9njmLR2z2S6K6VxQSb7QizYGUbAH3JU,960
|
|
85
85
|
utilities/uuid.py,sha256=jJTFxz-CWgltqNuzmythB7iEQ-Q1mCwPevUfKthZT3c,611
|
86
86
|
utilities/version.py,sha256=QFuyEeQA6jI0ruBEcmhqG36f-etg1AEiD1drBBqhQrs,5358
|
87
87
|
utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
|
88
|
-
utilities/whenever.py,sha256=
|
88
|
+
utilities/whenever.py,sha256=fC0ZtnO0AyFHsxP4SWj0POI1bf4BIL3Hh4rR51BHfaw,17803
|
89
89
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
90
90
|
utilities/zoneinfo.py,sha256=-Xm57PMMwDTYpxJdkiJG13wnbwK--I7XItBh5WVhD-o,1874
|
91
|
-
dycw_utilities-0.
|
92
|
-
dycw_utilities-0.
|
93
|
-
dycw_utilities-0.
|
94
|
-
dycw_utilities-0.
|
91
|
+
dycw_utilities-0.117.0.dist-info/METADATA,sha256=OB8XvTMe2rLu98khXHV7Y43VtZl_4iotbtejKesZsZg,12943
|
92
|
+
dycw_utilities-0.117.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
93
|
+
dycw_utilities-0.117.0.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
94
|
+
dycw_utilities-0.117.0.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/asyncio.py
CHANGED
@@ -41,7 +41,14 @@ from typing import (
|
|
41
41
|
override,
|
42
42
|
)
|
43
43
|
|
44
|
-
from utilities.datetime import
|
44
|
+
from utilities.datetime import (
|
45
|
+
MILLISECOND,
|
46
|
+
MINUTE,
|
47
|
+
SECOND,
|
48
|
+
datetime_duration_to_float,
|
49
|
+
get_now,
|
50
|
+
round_datetime,
|
51
|
+
)
|
45
52
|
from utilities.errors import ImpossibleCaseError, repr_error
|
46
53
|
from utilities.functions import ensure_int, ensure_not_none, get_class_name
|
47
54
|
from utilities.reprlib import get_repr
|
@@ -55,6 +62,7 @@ from utilities.types import (
|
|
55
62
|
)
|
56
63
|
|
57
64
|
if TYPE_CHECKING:
|
65
|
+
import datetime as dt
|
58
66
|
from asyncio import _CoroutineLike
|
59
67
|
from asyncio.subprocess import Process
|
60
68
|
from collections.abc import AsyncIterator, Sequence
|
@@ -490,6 +498,9 @@ class InfiniteQueueLooper(InfiniteLooper[THashable], Generic[THashable, _T]):
|
|
490
498
|
super().__post_init__()
|
491
499
|
self._queue = self.queue_type()
|
492
500
|
|
501
|
+
def __len__(self) -> int:
|
502
|
+
return self._queue.qsize()
|
503
|
+
|
493
504
|
@override
|
494
505
|
async def _core(self) -> None:
|
495
506
|
"""Run the core part of the loop."""
|
@@ -505,10 +516,19 @@ class InfiniteQueueLooper(InfiniteLooper[THashable], Generic[THashable, _T]):
|
|
505
516
|
async def _process_items(self, *items: _T) -> None:
|
506
517
|
"""Process the items."""
|
507
518
|
|
519
|
+
def empty(self) -> bool:
|
520
|
+
"""Check if the queue is empty."""
|
521
|
+
return self._queue.empty()
|
522
|
+
|
508
523
|
def put_items_nowait(self, *items: _T) -> None:
|
509
524
|
"""Put items into the queue."""
|
510
525
|
put_items_nowait(items, self._queue)
|
511
526
|
|
527
|
+
async def run_until_empty(self) -> None:
|
528
|
+
"""Run until the queue is empty."""
|
529
|
+
while not self.empty():
|
530
|
+
await self._process_items(*get_items_nowait(self._queue))
|
531
|
+
|
512
532
|
@override
|
513
533
|
def _error_upon_core(self, error: Exception, /) -> None:
|
514
534
|
"""Handle any errors upon running the core function."""
|
@@ -674,6 +694,27 @@ async def sleep_dur(*, duration: Duration | None = None) -> None:
|
|
674
694
|
##
|
675
695
|
|
676
696
|
|
697
|
+
async def sleep_until(datetime: dt.datetime, /) -> None:
|
698
|
+
"""Sleep until a given time."""
|
699
|
+
await sleep_dur(duration=datetime - get_now())
|
700
|
+
|
701
|
+
|
702
|
+
##
|
703
|
+
|
704
|
+
|
705
|
+
async def sleep_until_rounded(
|
706
|
+
duration: Duration, /, *, rel_tol: float | None = None, abs_tol: float | None = None
|
707
|
+
) -> None:
|
708
|
+
"""Sleep until a rounded time; accepts durations."""
|
709
|
+
datetime = round_datetime(
|
710
|
+
get_now(), duration, mode="ceil", rel_tol=rel_tol, abs_tol=abs_tol
|
711
|
+
)
|
712
|
+
await sleep_until(datetime)
|
713
|
+
|
714
|
+
|
715
|
+
##
|
716
|
+
|
717
|
+
|
677
718
|
@dataclass(kw_only=True, slots=True)
|
678
719
|
class StreamCommandOutput:
|
679
720
|
process: Process
|
@@ -756,6 +797,8 @@ __all__ = [
|
|
756
797
|
"put_items",
|
757
798
|
"put_items_nowait",
|
758
799
|
"sleep_dur",
|
800
|
+
"sleep_until",
|
801
|
+
"sleep_until_rounded",
|
759
802
|
"stream_command",
|
760
803
|
"timeout_dur",
|
761
804
|
]
|
utilities/datetime.py
CHANGED
@@ -334,6 +334,52 @@ def datetime_duration_to_float(duration: Duration, /) -> float:
|
|
334
334
|
assert_never(never)
|
335
335
|
|
336
336
|
|
337
|
+
def datetime_duration_to_microseconds(duration: Duration, /) -> int:
|
338
|
+
"""Compute the number of microseconds in a datetime duration."""
|
339
|
+
timedelta = datetime_duration_to_timedelta(duration)
|
340
|
+
return (
|
341
|
+
_MICROSECONDS_PER_DAY * timedelta.days
|
342
|
+
+ _MICROSECONDS_PER_SECOND * timedelta.seconds
|
343
|
+
+ timedelta.microseconds
|
344
|
+
)
|
345
|
+
|
346
|
+
|
347
|
+
@overload
|
348
|
+
def datetime_duration_to_milliseconds(
|
349
|
+
duration: Duration, /, *, strict: Literal[True]
|
350
|
+
) -> int: ...
|
351
|
+
@overload
|
352
|
+
def datetime_duration_to_milliseconds(
|
353
|
+
duration: Duration, /, *, strict: bool = False
|
354
|
+
) -> float: ...
|
355
|
+
def datetime_duration_to_milliseconds(
|
356
|
+
duration: Duration, /, *, strict: bool = False
|
357
|
+
) -> int | float:
|
358
|
+
"""Compute the number of milliseconds in a datetime duration."""
|
359
|
+
timedelta = datetime_duration_to_timedelta(duration)
|
360
|
+
microseconds = datetime_duration_to_microseconds(timedelta)
|
361
|
+
milliseconds, remainder = divmod(microseconds, _MICROSECONDS_PER_MILLISECOND)
|
362
|
+
match remainder, strict:
|
363
|
+
case 0, _:
|
364
|
+
return milliseconds
|
365
|
+
case _, True:
|
366
|
+
raise TimedeltaToMillisecondsError(duration=duration, remainder=remainder)
|
367
|
+
case _, False:
|
368
|
+
return milliseconds + remainder / _MICROSECONDS_PER_MILLISECOND
|
369
|
+
case _ as never:
|
370
|
+
assert_never(never)
|
371
|
+
|
372
|
+
|
373
|
+
@dataclass(kw_only=True, slots=True)
|
374
|
+
class TimedeltaToMillisecondsError(Exception):
|
375
|
+
duration: Duration
|
376
|
+
remainder: int
|
377
|
+
|
378
|
+
@override
|
379
|
+
def __str__(self) -> str:
|
380
|
+
return f"Unable to convert {self.duration} to milliseconds; got {self.remainder} microsecond(s)"
|
381
|
+
|
382
|
+
|
337
383
|
def datetime_duration_to_timedelta(duration: Duration, /) -> dt.timedelta:
|
338
384
|
"""Ensure a datetime duration is a timedelta."""
|
339
385
|
match duration:
|
@@ -651,8 +697,9 @@ YEAR = get_years(n=1)
|
|
651
697
|
##
|
652
698
|
|
653
699
|
|
654
|
-
def is_integral_timedelta(
|
655
|
-
"""Check if a
|
700
|
+
def is_integral_timedelta(duration: Duration, /) -> bool:
|
701
|
+
"""Check if a duration is integral."""
|
702
|
+
timedelta = datetime_duration_to_timedelta(duration)
|
656
703
|
return (timedelta.seconds == 0) and (timedelta.microseconds == 0)
|
657
704
|
|
658
705
|
|
@@ -679,9 +726,9 @@ def is_weekday(date: dt.date, /) -> bool:
|
|
679
726
|
##
|
680
727
|
|
681
728
|
|
682
|
-
def is_zero_time(
|
729
|
+
def is_zero_time(duration: Duration, /) -> bool:
|
683
730
|
"""Check if a timedelta is 0."""
|
684
|
-
return
|
731
|
+
return datetime_duration_to_timedelta(duration) == ZERO_TIME
|
685
732
|
|
686
733
|
|
687
734
|
##
|
@@ -763,7 +810,7 @@ def mean_timedelta(
|
|
763
810
|
case 1:
|
764
811
|
return one(timedeltas)
|
765
812
|
case _:
|
766
|
-
microseconds = list(map(
|
813
|
+
microseconds = list(map(datetime_duration_to_microseconds, timedeltas))
|
767
814
|
mean_float = fmean(microseconds, weights=weights)
|
768
815
|
mean_int = round_(mean_float, mode=mode, rel_tol=rel_tol, abs_tol=abs_tol)
|
769
816
|
return microseconds_to_timedelta(mean_int)
|
@@ -781,7 +828,7 @@ class MeanTimeDeltaError(Exception):
|
|
781
828
|
|
782
829
|
def microseconds_since_epoch(datetime: dt.datetime, /) -> int:
|
783
830
|
"""Compute the number of microseconds since the epoch."""
|
784
|
-
return
|
831
|
+
return datetime_duration_to_microseconds(timedelta_since_epoch(datetime))
|
785
832
|
|
786
833
|
|
787
834
|
def microseconds_to_timedelta(microseconds: int, /) -> dt.timedelta:
|
@@ -980,7 +1027,7 @@ class _ParseTwoDigitYearInvalidStringError(Exception):
|
|
980
1027
|
|
981
1028
|
def round_datetime(
|
982
1029
|
datetime: dt.datetime,
|
983
|
-
|
1030
|
+
duration: Duration,
|
984
1031
|
/,
|
985
1032
|
*,
|
986
1033
|
mode: RoundMode = "standard",
|
@@ -990,7 +1037,7 @@ def round_datetime(
|
|
990
1037
|
"""Round a datetime to a timedelta."""
|
991
1038
|
if datetime.tzinfo is None:
|
992
1039
|
dividend = microseconds_since_epoch(datetime)
|
993
|
-
divisor =
|
1040
|
+
divisor = datetime_duration_to_microseconds(duration)
|
994
1041
|
quotient, remainder = divmod(dividend, divisor)
|
995
1042
|
rnd_remainder = round_(
|
996
1043
|
remainder / divisor, mode=mode, rel_tol=rel_tol, abs_tol=abs_tol
|
@@ -1000,7 +1047,7 @@ def round_datetime(
|
|
1000
1047
|
return microseconds_since_epoch_to_datetime(microseconds)
|
1001
1048
|
local = datetime.replace(tzinfo=None)
|
1002
1049
|
rounded = round_datetime(
|
1003
|
-
local,
|
1050
|
+
local, duration, mode=mode, rel_tol=rel_tol, abs_tol=abs_tol
|
1004
1051
|
)
|
1005
1052
|
return rounded.replace(tzinfo=datetime.tzinfo)
|
1006
1053
|
|
@@ -1175,50 +1222,6 @@ def timedelta_since_epoch(date_or_datetime: DateOrDateTime, /) -> dt.timedelta:
|
|
1175
1222
|
assert_never(never)
|
1176
1223
|
|
1177
1224
|
|
1178
|
-
def timedelta_to_microseconds(timedelta: dt.timedelta, /) -> int:
|
1179
|
-
"""Compute the number of microseconds in a timedelta."""
|
1180
|
-
return (
|
1181
|
-
_MICROSECONDS_PER_DAY * timedelta.days
|
1182
|
-
+ _MICROSECONDS_PER_SECOND * timedelta.seconds
|
1183
|
-
+ timedelta.microseconds
|
1184
|
-
)
|
1185
|
-
|
1186
|
-
|
1187
|
-
@overload
|
1188
|
-
def timedelta_to_milliseconds(
|
1189
|
-
timedelta: dt.timedelta, /, *, strict: Literal[True]
|
1190
|
-
) -> int: ...
|
1191
|
-
@overload
|
1192
|
-
def timedelta_to_milliseconds(
|
1193
|
-
timedelta: dt.timedelta, /, *, strict: bool = False
|
1194
|
-
) -> float: ...
|
1195
|
-
def timedelta_to_milliseconds(
|
1196
|
-
timedelta: dt.timedelta, /, *, strict: bool = False
|
1197
|
-
) -> int | float:
|
1198
|
-
"""Compute the number of milliseconds in a timedelta."""
|
1199
|
-
microseconds = timedelta_to_microseconds(timedelta)
|
1200
|
-
milliseconds, remainder = divmod(microseconds, _MICROSECONDS_PER_MILLISECOND)
|
1201
|
-
match remainder, strict:
|
1202
|
-
case 0, _:
|
1203
|
-
return milliseconds
|
1204
|
-
case _, True:
|
1205
|
-
raise TimedeltaToMillisecondsError(timedelta=timedelta, remainder=remainder)
|
1206
|
-
case _, False:
|
1207
|
-
return milliseconds + remainder / _MICROSECONDS_PER_MILLISECOND
|
1208
|
-
case _ as never:
|
1209
|
-
assert_never(never)
|
1210
|
-
|
1211
|
-
|
1212
|
-
@dataclass(kw_only=True, slots=True)
|
1213
|
-
class TimedeltaToMillisecondsError(Exception):
|
1214
|
-
timedelta: dt.timedelta
|
1215
|
-
remainder: int
|
1216
|
-
|
1217
|
-
@override
|
1218
|
-
def __str__(self) -> str:
|
1219
|
-
return f"Unable to convert {self.timedelta} to milliseconds; got {self.remainder} microsecond(s)"
|
1220
|
-
|
1221
|
-
|
1222
1225
|
##
|
1223
1226
|
|
1224
1227
|
|
@@ -1367,6 +1370,8 @@ __all__ = [
|
|
1367
1370
|
"date_to_datetime",
|
1368
1371
|
"date_to_month",
|
1369
1372
|
"datetime_duration_to_float",
|
1373
|
+
"datetime_duration_to_microseconds",
|
1374
|
+
"datetime_duration_to_milliseconds",
|
1370
1375
|
"datetime_duration_to_timedelta",
|
1371
1376
|
"datetime_utc",
|
1372
1377
|
"days_since_epoch",
|
@@ -1407,8 +1412,6 @@ __all__ = [
|
|
1407
1412
|
"serialize_month",
|
1408
1413
|
"sub_duration",
|
1409
1414
|
"timedelta_since_epoch",
|
1410
|
-
"timedelta_to_microseconds",
|
1411
|
-
"timedelta_to_milliseconds",
|
1412
1415
|
"yield_days",
|
1413
1416
|
"yield_weekdays",
|
1414
1417
|
]
|
utilities/whenever.py
CHANGED
@@ -14,8 +14,8 @@ from utilities.datetime import (
|
|
14
14
|
_MICROSECONDS_PER_SECOND,
|
15
15
|
ZERO_TIME,
|
16
16
|
check_date_not_datetime,
|
17
|
+
datetime_duration_to_microseconds,
|
17
18
|
parse_two_digit_year,
|
18
|
-
timedelta_to_microseconds,
|
19
19
|
)
|
20
20
|
from utilities.math import ParseNumberError, parse_number
|
21
21
|
from utilities.re import (
|
@@ -601,7 +601,7 @@ class SerializeZonedDateTimeError(Exception):
|
|
601
601
|
|
602
602
|
def _to_datetime_delta(timedelta: dt.timedelta, /) -> DateTimeDelta:
|
603
603
|
"""Serialize a timedelta."""
|
604
|
-
total_microseconds =
|
604
|
+
total_microseconds = datetime_duration_to_microseconds(timedelta)
|
605
605
|
if total_microseconds == 0:
|
606
606
|
return DateTimeDelta()
|
607
607
|
if total_microseconds >= 1:
|
File without changes
|
File without changes
|