dycw-utilities 0.113.1__py3-none-any.whl → 0.113.3__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,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.113.1
3
+ Version: 0.113.3
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; extra == 'test'
9
+ Requires-Dist: hypothesis<6.132,>=6.131.14; 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'
@@ -16,7 +16,7 @@ Requires-Dist: pytest-randomly<3.17,>=3.16.0; extra == 'test'
16
16
  Requires-Dist: pytest-regressions<2.8,>=2.7.0; extra == 'test'
17
17
  Requires-Dist: pytest-rerunfailures<16,>=15.0; extra == 'test'
18
18
  Requires-Dist: pytest-rng<1.1,>=1.0.0; extra == 'test'
19
- Requires-Dist: pytest-timeout<2.4,>=2.3.1; extra == 'test'
19
+ Requires-Dist: pytest-timeout<2.5,>=2.4.0; extra == 'test'
20
20
  Requires-Dist: pytest-xdist<3.7,>=3.6.1; extra == 'test'
21
21
  Requires-Dist: pytest<8.4,>=8.3.5; extra == 'test'
22
22
  Requires-Dist: setuptools>=78.0.2; 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.9; extra == 'zzz-test-hypothesis'
83
+ Requires-Dist: hypothesis<6.132,>=6.131.14; 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=jYMM0M9M6TZGpXyYU7Rwr3r3k0NY3IhjcbJz_O8C9oM,60
1
+ utilities/__init__.py,sha256=eOSRaK1eqblBSlkDTnJ-3-xWW49Ql-RkfxbwvIfUGyQ,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=41oQUurWMvadFK5gFnaG21hMM0Vmfn2WS6OpC0R9mas,14757
4
+ utilities/asyncio.py,sha256=6Pv0xK2jP55iF6EYjY1g6_rFGOeJyihxRNxXnOsWv14,15691
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=HyOewI20cl3uRXsKivhIaeLVDInQdzgXZGaly7hS5dE,771
13
13
  utilities/cvxpy.py,sha256=Rv1-fD-XYerosCavRF8Pohop2DBkU3AlFaGTfD8AEAA,13776
14
14
  utilities/dataclasses.py,sha256=iiC1wpGXWhaocIikzwBt8bbLWyImoUlOlcDZJGejaIg,33011
15
- utilities/datetime.py,sha256=OF7jZE702UecnwAbq9D3N-GINpp9gSGoidki1RhimCE,35752
15
+ utilities/datetime.py,sha256=PcN-4_sSPX1zbpdzBQRdo08pubCuGHyigxkV6SUnvlo,38733
16
16
  utilities/enum.py,sha256=HoRwVCWzsnH0vpO9ZEcAAIZLMv0Sn2vJxxA4sYMQgDs,5793
17
17
  utilities/errors.py,sha256=BtSNP0JC3ik536ddPyTerLomCRJV9f6kdMe6POz0QHM,361
18
18
  utilities/eventkit.py,sha256=6M5Xu1SzN-juk9PqBHwy5dS-ta7T0qA6SMpDsakOJ0E,13039
@@ -77,7 +77,7 @@ utilities/text.py,sha256=Fo12N4aA7k2rnb4W4vH9iiDh88Q5_nvRssTkfNsvVM8,10965
77
77
  utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
78
78
  utilities/timer.py,sha256=Rkc49KSpHuC8s7vUxGO9DU55U9I6yDKnchsQqrUCVBs,4075
79
79
  utilities/traceback.py,sha256=secexUnBsecfWV4ZuqP1W4pGF3prOeO1CRyJK-8zQDU,27402
80
- utilities/types.py,sha256=B5slv6Vdj6uRmE1xnEBEJfWJmK_0odEwIUCVC9KHikw,18168
80
+ utilities/types.py,sha256=FhE1b8v_s_IlmXucwY7jAMWAq9cfpzyKssQwgwb3jnM,18267
81
81
  utilities/typing.py,sha256=H6ysJkI830aRwLsMKz0SZIw4cpcsm7d6KhQOwr-SDh0,13817
82
82
  utilities/tzdata.py,sha256=yCf70NICwAeazN3_JcXhWvRqCy06XJNQ42j7r6gw3HY,1217
83
83
  utilities/tzlocal.py,sha256=3upDNFBvGh1l9njmLR2z2S6K6VxQSb7QizYGUbAH3JU,960
@@ -87,7 +87,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
87
87
  utilities/whenever.py,sha256=iLRP_-8CZtBpHKbGZGu-kjSMg1ZubJ-VSmgSy7Eudxw,17787
88
88
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
89
89
  utilities/zoneinfo.py,sha256=-Xm57PMMwDTYpxJdkiJG13wnbwK--I7XItBh5WVhD-o,1874
90
- dycw_utilities-0.113.1.dist-info/METADATA,sha256=yaeQ1FFLx3bZShYnRwFit1bF6k9iieYw8kInaN6kQ2w,12941
91
- dycw_utilities-0.113.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
- dycw_utilities-0.113.1.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
93
- dycw_utilities-0.113.1.dist-info/RECORD,,
90
+ dycw_utilities-0.113.3.dist-info/METADATA,sha256=FekxkXInaGKIntNnsuUBc8VxfVEcuyBaoxzXdhhdxII,12943
91
+ dycw_utilities-0.113.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
+ dycw_utilities-0.113.3.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
93
+ dycw_utilities-0.113.3.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.113.1"
3
+ __version__ = "0.113.3"
utilities/asyncio.py CHANGED
@@ -17,6 +17,7 @@ from asyncio import (
17
17
  sleep,
18
18
  timeout,
19
19
  )
20
+ from collections.abc import Callable
20
21
  from contextlib import (
21
22
  AsyncExitStack,
22
23
  _AsyncGeneratorContextManager,
@@ -27,12 +28,22 @@ from dataclasses import dataclass, field
27
28
  from io import StringIO
28
29
  from subprocess import PIPE
29
30
  from sys import stderr, stdout
30
- from typing import TYPE_CHECKING, Generic, Self, TextIO, TypeVar, override
31
+ from typing import (
32
+ TYPE_CHECKING,
33
+ Generic,
34
+ Self,
35
+ TextIO,
36
+ TypeVar,
37
+ assert_never,
38
+ overload,
39
+ override,
40
+ )
31
41
 
32
42
  from utilities.datetime import MILLISECOND, datetime_duration_to_float
33
43
  from utilities.errors import ImpossibleCaseError
34
44
  from utilities.functions import ensure_int, ensure_not_none
35
- from utilities.types import THashable, TSupportsRichComparison
45
+ from utilities.sentinel import Sentinel, sentinel
46
+ from utilities.types import MaybeCallableEvent, THashable, TSupportsRichComparison
36
47
 
37
48
  if TYPE_CHECKING:
38
49
  from asyncio import _CoroutineLike
@@ -356,6 +367,34 @@ class UniqueQueue(Queue[THashable]):
356
367
  ##
357
368
 
358
369
 
370
+ @overload
371
+ def get_event(*, event: MaybeCallableEvent) -> Event: ...
372
+ @overload
373
+ def get_event(*, event: None) -> None: ...
374
+ @overload
375
+ def get_event(*, event: Sentinel) -> Sentinel: ...
376
+ @overload
377
+ def get_event(*, event: MaybeCallableEvent | Sentinel) -> Event | Sentinel: ...
378
+ @overload
379
+ def get_event(
380
+ *, event: MaybeCallableEvent | None | Sentinel = sentinel
381
+ ) -> Event | None | Sentinel: ...
382
+ def get_event(
383
+ *, event: MaybeCallableEvent | None | Sentinel = sentinel
384
+ ) -> Event | None | Sentinel:
385
+ """Get the event."""
386
+ match event:
387
+ case Event() | None | Sentinel():
388
+ return event
389
+ case Callable() as func:
390
+ return get_event(event=func())
391
+ case _ as never:
392
+ assert_never(never)
393
+
394
+
395
+ ##
396
+
397
+
359
398
  async def get_items(
360
399
  queue: Queue[_T], /, *, max_size: int | None = None, lock: Lock | None = None
361
400
  ) -> list[_T]:
@@ -490,6 +529,7 @@ __all__ = [
490
529
  "StreamCommandOutput",
491
530
  "UniquePriorityQueue",
492
531
  "UniqueQueue",
532
+ "get_event",
493
533
  "get_items",
494
534
  "get_items_nowait",
495
535
  "sleep_dur",
utilities/datetime.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import datetime as dt
4
- from collections.abc import Callable, Iterable
4
+ from collections.abc import Callable, Iterable, Sequence
5
5
  from dataclasses import dataclass, replace
6
6
  from re import search, sub
7
7
  from statistics import fmean
@@ -495,6 +495,101 @@ def get_half_years(*, n: int = 1) -> dt.timedelta:
495
495
 
496
496
  HALF_YEAR = get_half_years(n=1)
497
497
 
498
+ ##
499
+
500
+
501
+ def get_min_max_date(
502
+ *,
503
+ min_date: dt.date | None = None,
504
+ max_date: dt.date | None = None,
505
+ min_age: Duration | None = None,
506
+ max_age: Duration | None = None,
507
+ time_zone: TimeZoneLike = UTC,
508
+ ) -> tuple[dt.date | None, dt.date | None]:
509
+ """Get the min/max date given a combination of dates/ages."""
510
+ today = get_today(time_zone=time_zone)
511
+ min_parts: Sequence[dt.date] = []
512
+ if min_date is not None:
513
+ if min_date > today:
514
+ raise _GetMinMaxDateMinDateError(min_date=min_date, today=today)
515
+ min_parts.append(min_date)
516
+ if max_age is not None:
517
+ if date_duration_to_timedelta(max_age) < ZERO_TIME:
518
+ raise _GetMinMaxDateMaxAgeError(max_age=max_age)
519
+ min_parts.append(sub_duration(today, duration=max_age))
520
+ min_date_use = max(min_parts, default=None)
521
+ max_parts: Sequence[dt.date] = []
522
+ if max_date is not None:
523
+ if max_date > today:
524
+ raise _GetMinMaxDateMaxDateError(max_date=max_date, today=today)
525
+ max_parts.append(max_date)
526
+ if min_age is not None:
527
+ if date_duration_to_timedelta(min_age) < ZERO_TIME:
528
+ raise _GetMinMaxDateMinAgeError(min_age=min_age)
529
+ max_parts.append(sub_duration(today, duration=min_age))
530
+ max_date_use = min(max_parts, default=None)
531
+ if (
532
+ (min_date_use is not None)
533
+ and (max_date_use is not None)
534
+ and (min_date_use > max_date_use)
535
+ ):
536
+ raise _GetMinMaxDatePeriodError(min_date=min_date_use, max_date=max_date_use)
537
+ return min_date_use, max_date_use
538
+
539
+
540
+ @dataclass(kw_only=True, slots=True)
541
+ class GetMinMaxDateError(Exception): ...
542
+
543
+
544
+ @dataclass(kw_only=True, slots=True)
545
+ class _GetMinMaxDateMinDateError(GetMinMaxDateError):
546
+ min_date: dt.date
547
+ today: dt.date
548
+
549
+ @override
550
+ def __str__(self) -> str:
551
+ return f"Min date must be at most today; got {self.min_date} > {self.today}"
552
+
553
+
554
+ @dataclass(kw_only=True, slots=True)
555
+ class _GetMinMaxDateMinAgeError(GetMinMaxDateError):
556
+ min_age: Duration
557
+
558
+ @override
559
+ def __str__(self) -> str:
560
+ return f"Min age must be non-negative; got {self.min_age}"
561
+
562
+
563
+ @dataclass(kw_only=True, slots=True)
564
+ class _GetMinMaxDateMaxDateError(GetMinMaxDateError):
565
+ max_date: dt.date
566
+ today: dt.date
567
+
568
+ @override
569
+ def __str__(self) -> str:
570
+ return f"Max date must be at most today; got {self.max_date} > {self.today}"
571
+
572
+
573
+ @dataclass(kw_only=True, slots=True)
574
+ class _GetMinMaxDateMaxAgeError(GetMinMaxDateError):
575
+ max_age: Duration
576
+
577
+ @override
578
+ def __str__(self) -> str:
579
+ return f"Max age must be non-negative; got {self.max_age}"
580
+
581
+
582
+ @dataclass(kw_only=True, slots=True)
583
+ class _GetMinMaxDatePeriodError(GetMinMaxDateError):
584
+ min_date: dt.date
585
+ max_date: dt.date
586
+
587
+ @override
588
+ def __str__(self) -> str:
589
+ return (
590
+ f"Min date must be at most max date; got {self.min_date} > {self.max_date}"
591
+ )
592
+
498
593
 
499
594
  ##
500
595
 
@@ -1244,6 +1339,7 @@ __all__ = [
1244
1339
  "CheckDateNotDateTimeError",
1245
1340
  "DateOrMonth",
1246
1341
  "EnsureMonthError",
1342
+ "GetMinMaxDateError",
1247
1343
  "MeanDateTimeError",
1248
1344
  "MeanTimeDeltaError",
1249
1345
  "MillisecondsSinceEpochError",
@@ -1280,6 +1376,7 @@ __all__ = [
1280
1376
  "get_date",
1281
1377
  "get_datetime",
1282
1378
  "get_half_years",
1379
+ "get_min_max_date",
1283
1380
  "get_months",
1284
1381
  "get_now",
1285
1382
  "get_quarters",
utilities/types.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import datetime as dt
4
+ from asyncio import Event
4
5
  from collections.abc import Awaitable, Callable, Coroutine, Hashable, Iterable, Mapping
5
6
  from enum import Enum
6
7
  from logging import Logger
@@ -57,6 +58,7 @@ type TupleOrStrMapping = tuple[Any, ...] | StrMapping
57
58
  # asyncio
58
59
  type Coroutine1[_T] = Coroutine[Any, Any, _T]
59
60
  type MaybeAwaitable[_T] = _T | Awaitable[_T]
61
+ type MaybeCallableEvent = MaybeCallable[Event]
60
62
  type MaybeCoroutine1[_T] = _T | Coroutine1[_T]
61
63
 
62
64
 
@@ -277,6 +279,7 @@ __all__ = [
277
279
  "MaybeCallable",
278
280
  "MaybeCallableDate",
279
281
  "MaybeCallableDateTime",
282
+ "MaybeCallableEvent",
280
283
  "MaybeCoroutine1",
281
284
  "MaybeIterable",
282
285
  "MaybeIterableHashable",