dycw-utilities 0.131.16__py3-none-any.whl → 0.131.18__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.131.16.dist-info → dycw_utilities-0.131.18.dist-info}/METADATA +1 -1
- {dycw_utilities-0.131.16.dist-info → dycw_utilities-0.131.18.dist-info}/RECORD +18 -19
- utilities/__init__.py +1 -1
- utilities/asyncio.py +47 -60
- utilities/datetime.py +2 -279
- utilities/iterables.py +1 -30
- utilities/operator.py +21 -15
- utilities/orjson.py +67 -84
- utilities/parse.py +24 -53
- utilities/pottery.py +16 -19
- utilities/psutil.py +8 -7
- utilities/redis.py +108 -116
- utilities/slack_sdk.py +17 -18
- utilities/sqlalchemy.py +36 -30
- utilities/sqlalchemy_polars.py +12 -27
- utilities/whenever.py +2 -216
- utilities/tenacity.py +0 -145
- {dycw_utilities-0.131.16.dist-info → dycw_utilities-0.131.18.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.131.16.dist-info → dycw_utilities-0.131.18.dist-info}/licenses/LICENSE +0 -0
utilities/datetime.py
CHANGED
@@ -11,7 +11,6 @@ from typing import (
|
|
11
11
|
Literal,
|
12
12
|
Self,
|
13
13
|
SupportsFloat,
|
14
|
-
TypeGuard,
|
15
14
|
assert_never,
|
16
15
|
overload,
|
17
16
|
override,
|
@@ -23,7 +22,7 @@ from utilities.platform import SYSTEM
|
|
23
22
|
from utilities.sentinel import Sentinel, sentinel
|
24
23
|
from utilities.types import MaybeCallablePyDate, MaybeCallablePyDateTime, MaybeStr
|
25
24
|
from utilities.typing import is_instance_gen
|
26
|
-
from utilities.zoneinfo import UTC, ensure_time_zone
|
25
|
+
from utilities.zoneinfo import UTC, ensure_time_zone
|
27
26
|
|
28
27
|
if TYPE_CHECKING:
|
29
28
|
from collections.abc import Iterator
|
@@ -114,50 +113,6 @@ class AddWeekdaysError(Exception): ...
|
|
114
113
|
##
|
115
114
|
|
116
115
|
|
117
|
-
def are_equal_date_durations(x: Duration, y: Duration, /) -> bool:
|
118
|
-
"""Check if x == y for durations."""
|
119
|
-
x_timedelta = date_duration_to_timedelta(x)
|
120
|
-
y_timedelta = date_duration_to_timedelta(y)
|
121
|
-
return x_timedelta == y_timedelta
|
122
|
-
|
123
|
-
|
124
|
-
##
|
125
|
-
|
126
|
-
|
127
|
-
def are_equal_dates_or_datetimes(
|
128
|
-
x: DateOrDateTime, y: DateOrDateTime, /, *, strict: bool = False
|
129
|
-
) -> bool:
|
130
|
-
"""Check if x == y for dates/datetimes."""
|
131
|
-
if is_instance_gen(x, dt.date) and is_instance_gen(y, dt.date):
|
132
|
-
return x == y
|
133
|
-
if is_instance_gen(x, dt.datetime) and is_instance_gen(y, dt.datetime):
|
134
|
-
return are_equal_datetimes(x, y, strict=strict)
|
135
|
-
raise AreEqualDatesOrDateTimesError(x=x, y=y)
|
136
|
-
|
137
|
-
|
138
|
-
@dataclass(kw_only=True, slots=True)
|
139
|
-
class AreEqualDatesOrDateTimesError(Exception):
|
140
|
-
x: DateOrDateTime
|
141
|
-
y: DateOrDateTime
|
142
|
-
|
143
|
-
@override
|
144
|
-
def __str__(self) -> str:
|
145
|
-
return f"Cannot compare date and datetime ({self.x}, {self.y})"
|
146
|
-
|
147
|
-
|
148
|
-
##
|
149
|
-
|
150
|
-
|
151
|
-
def are_equal_datetime_durations(x: Duration, y: Duration, /) -> bool:
|
152
|
-
"""Check if x == y for durations."""
|
153
|
-
x_timedelta = datetime_duration_to_timedelta(x)
|
154
|
-
y_timedelta = datetime_duration_to_timedelta(y)
|
155
|
-
return x_timedelta == y_timedelta
|
156
|
-
|
157
|
-
|
158
|
-
##
|
159
|
-
|
160
|
-
|
161
116
|
def are_equal_datetimes(
|
162
117
|
x: dt.datetime, y: dt.datetime, /, *, strict: bool = False
|
163
118
|
) -> bool:
|
@@ -186,16 +141,6 @@ class AreEqualDateTimesError(Exception):
|
|
186
141
|
##
|
187
142
|
|
188
143
|
|
189
|
-
def are_equal_months(x: DateOrMonth, y: DateOrMonth, /) -> bool:
|
190
|
-
"""Check if x == y as months."""
|
191
|
-
x_month = Month.from_date(x) if isinstance(x, dt.date) else x
|
192
|
-
y_month = Month.from_date(y) if isinstance(y, dt.date) else y
|
193
|
-
return x_month == y_month
|
194
|
-
|
195
|
-
|
196
|
-
##
|
197
|
-
|
198
|
-
|
199
144
|
def check_date_not_datetime(date: dt.date, /) -> None:
|
200
145
|
"""Check if a date is not a datetime."""
|
201
146
|
if not is_instance_gen(date, dt.date):
|
@@ -214,19 +159,6 @@ class CheckDateNotDateTimeError(Exception):
|
|
214
159
|
##
|
215
160
|
|
216
161
|
|
217
|
-
def date_to_datetime(
|
218
|
-
date: dt.date, /, *, time: dt.time | None = None, time_zone: TimeZoneLike = UTC
|
219
|
-
) -> dt.datetime:
|
220
|
-
"""Expand a date into a datetime."""
|
221
|
-
check_date_not_datetime(date)
|
222
|
-
time_use = dt.time(0) if time is None else time
|
223
|
-
time_zone_use = ensure_time_zone(time_zone)
|
224
|
-
return dt.datetime.combine(date, time_use, tzinfo=time_zone_use)
|
225
|
-
|
226
|
-
|
227
|
-
##
|
228
|
-
|
229
|
-
|
230
162
|
def date_to_month(date: dt.date, /) -> Month:
|
231
163
|
"""Collapse a date into a month."""
|
232
164
|
check_date_not_datetime(date)
|
@@ -337,32 +269,6 @@ def datetime_duration_to_microseconds(duration: Duration, /) -> int:
|
|
337
269
|
)
|
338
270
|
|
339
271
|
|
340
|
-
@overload
|
341
|
-
def datetime_duration_to_milliseconds(
|
342
|
-
duration: Duration, /, *, strict: Literal[True]
|
343
|
-
) -> int: ...
|
344
|
-
@overload
|
345
|
-
def datetime_duration_to_milliseconds(
|
346
|
-
duration: Duration, /, *, strict: bool = False
|
347
|
-
) -> float: ...
|
348
|
-
def datetime_duration_to_milliseconds(
|
349
|
-
duration: Duration, /, *, strict: bool = False
|
350
|
-
) -> int | float:
|
351
|
-
"""Compute the number of milliseconds in a datetime duration."""
|
352
|
-
timedelta = datetime_duration_to_timedelta(duration)
|
353
|
-
microseconds = datetime_duration_to_microseconds(timedelta)
|
354
|
-
milliseconds, remainder = divmod(microseconds, _MICROSECONDS_PER_MILLISECOND)
|
355
|
-
match remainder, strict:
|
356
|
-
case 0, _:
|
357
|
-
return milliseconds
|
358
|
-
case _, True:
|
359
|
-
raise TimedeltaToMillisecondsError(duration=duration, remainder=remainder)
|
360
|
-
case _, False:
|
361
|
-
return milliseconds + remainder / _MICROSECONDS_PER_MILLISECOND
|
362
|
-
case _ as never:
|
363
|
-
assert_never(never)
|
364
|
-
|
365
|
-
|
366
272
|
@dataclass(kw_only=True, slots=True)
|
367
273
|
class TimedeltaToMillisecondsError(Exception):
|
368
274
|
duration: Duration
|
@@ -370,7 +276,7 @@ class TimedeltaToMillisecondsError(Exception):
|
|
370
276
|
|
371
277
|
@override
|
372
278
|
def __str__(self) -> str:
|
373
|
-
return f"Unable to convert {self.duration} to milliseconds; got {self.remainder} microsecond(s)"
|
279
|
+
return f"Unable to convert {self.duration} to milliseconds; got {self.remainder} microsecond(s)" # pragma: no cover
|
374
280
|
|
375
281
|
|
376
282
|
def datetime_duration_to_timedelta(duration: Duration, /) -> dt.timedelta:
|
@@ -413,20 +319,6 @@ def datetime_utc(
|
|
413
319
|
##
|
414
320
|
|
415
321
|
|
416
|
-
def days_since_epoch(date: dt.date, /) -> int:
|
417
|
-
"""Compute the number of days since the epoch."""
|
418
|
-
check_date_not_datetime(date)
|
419
|
-
return timedelta_since_epoch(date).days
|
420
|
-
|
421
|
-
|
422
|
-
def days_since_epoch_to_date(days: int, /) -> dt.date:
|
423
|
-
"""Convert a number of days since the epoch to a date."""
|
424
|
-
return EPOCH_DATE + days * DAY
|
425
|
-
|
426
|
-
|
427
|
-
##
|
428
|
-
|
429
|
-
|
430
322
|
def ensure_month(month: MonthLike, /) -> Month:
|
431
323
|
"""Ensure the object is a month."""
|
432
324
|
if isinstance(month, Month):
|
@@ -449,25 +341,6 @@ class EnsureMonthError(Exception):
|
|
449
341
|
##
|
450
342
|
|
451
343
|
|
452
|
-
def format_datetime_local_and_utc(datetime: dt.datetime, /) -> str:
|
453
|
-
"""Format a plain datetime locally & in UTC."""
|
454
|
-
time_zone = ensure_time_zone(datetime)
|
455
|
-
if time_zone is UTC:
|
456
|
-
return datetime.strftime("%Y-%m-%d %H:%M:%S (%a, UTC)")
|
457
|
-
as_utc = datetime.astimezone(UTC)
|
458
|
-
local = get_time_zone_name(time_zone)
|
459
|
-
if datetime.year != as_utc.year:
|
460
|
-
return f"{datetime:%Y-%m-%d %H:%M:%S (%a}, {local}, {as_utc:%Y-%m-%d %H:%M:%S} UTC)"
|
461
|
-
if (datetime.month != as_utc.month) or (datetime.day != as_utc.day):
|
462
|
-
return (
|
463
|
-
f"{datetime:%Y-%m-%d %H:%M:%S (%a}, {local}, {as_utc:%m-%d %H:%M:%S} UTC)"
|
464
|
-
)
|
465
|
-
return f"{datetime:%Y-%m-%d %H:%M:%S (%a}, {local}, {as_utc:%H:%M:%S} UTC)"
|
466
|
-
|
467
|
-
|
468
|
-
##
|
469
|
-
|
470
|
-
|
471
344
|
@overload
|
472
345
|
def get_date(*, date: MaybeCallablePyDate) -> dt.date: ...
|
473
346
|
@overload
|
@@ -691,14 +564,6 @@ def is_integral_timedelta(duration: Duration, /) -> bool:
|
|
691
564
|
##
|
692
565
|
|
693
566
|
|
694
|
-
def is_plain_datetime(obj: Any, /) -> TypeGuard[dt.datetime]:
|
695
|
-
"""Check if an object is a plain datetime."""
|
696
|
-
return isinstance(obj, dt.datetime) and (obj.tzinfo is None)
|
697
|
-
|
698
|
-
|
699
|
-
##
|
700
|
-
|
701
|
-
|
702
567
|
_FRIDAY = 5
|
703
568
|
|
704
569
|
|
@@ -719,14 +584,6 @@ def is_zero_time(duration: Duration, /) -> bool:
|
|
719
584
|
##
|
720
585
|
|
721
586
|
|
722
|
-
def is_zoned_datetime(obj: Any, /) -> TypeGuard[dt.datetime]:
|
723
|
-
"""Check if an object is a zoned datetime."""
|
724
|
-
return isinstance(obj, dt.datetime) and (obj.tzinfo is not None)
|
725
|
-
|
726
|
-
|
727
|
-
##
|
728
|
-
|
729
|
-
|
730
587
|
def maybe_sub_pct_y(text: str, /) -> str:
|
731
588
|
"""Substitute the `%Y' token with '%4Y' if necessary."""
|
732
589
|
match SYSTEM:
|
@@ -839,54 +696,6 @@ def microseconds_since_epoch_to_datetime(
|
|
839
696
|
##
|
840
697
|
|
841
698
|
|
842
|
-
@overload
|
843
|
-
def milliseconds_since_epoch(
|
844
|
-
datetime: dt.datetime, /, *, strict: Literal[True]
|
845
|
-
) -> int: ...
|
846
|
-
@overload
|
847
|
-
def milliseconds_since_epoch(
|
848
|
-
datetime: dt.datetime, /, *, strict: bool = False
|
849
|
-
) -> float: ...
|
850
|
-
def milliseconds_since_epoch(
|
851
|
-
datetime: dt.datetime, /, *, strict: bool = False
|
852
|
-
) -> float:
|
853
|
-
"""Compute the number of milliseconds since the epoch."""
|
854
|
-
microseconds = microseconds_since_epoch(datetime)
|
855
|
-
milliseconds, remainder = divmod(microseconds, _MICROSECONDS_PER_MILLISECOND)
|
856
|
-
if strict:
|
857
|
-
if remainder == 0:
|
858
|
-
return milliseconds
|
859
|
-
raise MillisecondsSinceEpochError(datetime=datetime, remainder=remainder)
|
860
|
-
return milliseconds + remainder / _MICROSECONDS_PER_MILLISECOND
|
861
|
-
|
862
|
-
|
863
|
-
@dataclass(kw_only=True, slots=True)
|
864
|
-
class MillisecondsSinceEpochError(Exception):
|
865
|
-
datetime: dt.datetime
|
866
|
-
remainder: int
|
867
|
-
|
868
|
-
@override
|
869
|
-
def __str__(self) -> str:
|
870
|
-
return f"Unable to convert {self.datetime} to milliseconds since epoch; got {self.remainder} microsecond(s)"
|
871
|
-
|
872
|
-
|
873
|
-
def milliseconds_since_epoch_to_datetime(
|
874
|
-
milliseconds: int, /, *, time_zone: dt.tzinfo | None = None
|
875
|
-
) -> dt.datetime:
|
876
|
-
"""Convert a number of milliseconds since the epoch to a datetime."""
|
877
|
-
epoch = EPOCH_NAIVE if time_zone is None else EPOCH_UTC
|
878
|
-
timedelta = milliseconds_to_timedelta(milliseconds)
|
879
|
-
return epoch + timedelta
|
880
|
-
|
881
|
-
|
882
|
-
def milliseconds_to_timedelta(milliseconds: int, /) -> dt.timedelta:
|
883
|
-
"""Compute a timedelta given a number of milliseconds."""
|
884
|
-
return microseconds_to_timedelta(_MICROSECONDS_PER_MILLISECOND * milliseconds)
|
885
|
-
|
886
|
-
|
887
|
-
##
|
888
|
-
|
889
|
-
|
890
699
|
@dataclass(order=True, unsafe_hash=True, slots=True)
|
891
700
|
class Month:
|
892
701
|
"""Represents a month in time."""
|
@@ -1070,70 +879,6 @@ def _round_to_weekday(
|
|
1070
879
|
##
|
1071
880
|
|
1072
881
|
|
1073
|
-
def serialize_compact(date_or_datetime: DateOrDateTime, /) -> str:
|
1074
|
-
"""Serialize a date/datetime using a compact format."""
|
1075
|
-
match date_or_datetime:
|
1076
|
-
case dt.datetime() as datetime:
|
1077
|
-
if datetime.tzinfo is None:
|
1078
|
-
raise SerializeCompactError(datetime=datetime)
|
1079
|
-
format_ = "%Y%m%dT%H%M%S"
|
1080
|
-
case dt.date():
|
1081
|
-
format_ = "%Y%m%d"
|
1082
|
-
case _ as never:
|
1083
|
-
assert_never(never)
|
1084
|
-
return date_or_datetime.strftime(maybe_sub_pct_y(format_))
|
1085
|
-
|
1086
|
-
|
1087
|
-
@dataclass(kw_only=True, slots=True)
|
1088
|
-
class SerializeCompactError(Exception):
|
1089
|
-
datetime: dt.datetime
|
1090
|
-
|
1091
|
-
@override
|
1092
|
-
def __str__(self) -> str:
|
1093
|
-
return f"Unable to serialize plain datetime {self.datetime}"
|
1094
|
-
|
1095
|
-
|
1096
|
-
def parse_date_compact(text: str, /) -> dt.date:
|
1097
|
-
"""Parse a compact string into a date."""
|
1098
|
-
try:
|
1099
|
-
datetime = dt.datetime.strptime(text, "%Y%m%d").replace(tzinfo=UTC)
|
1100
|
-
except ValueError:
|
1101
|
-
raise ParseDateCompactError(text=text) from None
|
1102
|
-
return datetime.date()
|
1103
|
-
|
1104
|
-
|
1105
|
-
@dataclass(kw_only=True, slots=True)
|
1106
|
-
class ParseDateCompactError(Exception):
|
1107
|
-
text: str
|
1108
|
-
|
1109
|
-
@override
|
1110
|
-
def __str__(self) -> str:
|
1111
|
-
return f"Unable to parse {self.text!r} into a date"
|
1112
|
-
|
1113
|
-
|
1114
|
-
def parse_datetime_compact(
|
1115
|
-
text: str, /, *, time_zone: TimeZoneLike = UTC
|
1116
|
-
) -> dt.datetime:
|
1117
|
-
"""Parse a compact string into a datetime."""
|
1118
|
-
time_zone = ensure_time_zone(time_zone)
|
1119
|
-
try:
|
1120
|
-
return dt.datetime.strptime(text, "%Y%m%dT%H%M%S").replace(tzinfo=time_zone)
|
1121
|
-
except ValueError:
|
1122
|
-
raise ParseDateTimeCompactError(text=text) from None
|
1123
|
-
|
1124
|
-
|
1125
|
-
@dataclass(kw_only=True, slots=True)
|
1126
|
-
class ParseDateTimeCompactError(Exception):
|
1127
|
-
text: str
|
1128
|
-
|
1129
|
-
@override
|
1130
|
-
def __str__(self) -> str:
|
1131
|
-
return f"Unable to parse {self.text!r} into a datetime"
|
1132
|
-
|
1133
|
-
|
1134
|
-
##
|
1135
|
-
|
1136
|
-
|
1137
882
|
def serialize_month(month: Month, /) -> str:
|
1138
883
|
"""Serialize a month."""
|
1139
884
|
return f"{month.year:04}-{month.month:02}"
|
@@ -1323,46 +1068,32 @@ __all__ = [
|
|
1323
1068
|
"AddDurationError",
|
1324
1069
|
"AddWeekdaysError",
|
1325
1070
|
"AreEqualDateTimesError",
|
1326
|
-
"AreEqualDatesOrDateTimesError",
|
1327
1071
|
"CheckDateNotDateTimeError",
|
1328
1072
|
"DateOrMonth",
|
1329
1073
|
"EnsureMonthError",
|
1330
1074
|
"GetMinMaxDateError",
|
1331
1075
|
"MeanDateTimeError",
|
1332
1076
|
"MeanTimeDeltaError",
|
1333
|
-
"MillisecondsSinceEpochError",
|
1334
1077
|
"Month",
|
1335
1078
|
"MonthError",
|
1336
1079
|
"MonthLike",
|
1337
|
-
"ParseDateCompactError",
|
1338
|
-
"ParseDateTimeCompactError",
|
1339
1080
|
"ParseMonthError",
|
1340
|
-
"SerializeCompactError",
|
1341
1081
|
"SubDurationError",
|
1342
1082
|
"TimedeltaToMillisecondsError",
|
1343
1083
|
"YieldDaysError",
|
1344
1084
|
"YieldWeekdaysError",
|
1345
1085
|
"add_duration",
|
1346
1086
|
"add_weekdays",
|
1347
|
-
"are_equal_date_durations",
|
1348
|
-
"are_equal_dates_or_datetimes",
|
1349
|
-
"are_equal_datetime_durations",
|
1350
1087
|
"are_equal_datetimes",
|
1351
|
-
"are_equal_months",
|
1352
1088
|
"check_date_not_datetime",
|
1353
1089
|
"date_duration_to_int",
|
1354
1090
|
"date_duration_to_timedelta",
|
1355
|
-
"date_to_datetime",
|
1356
1091
|
"date_to_month",
|
1357
1092
|
"datetime_duration_to_float",
|
1358
1093
|
"datetime_duration_to_microseconds",
|
1359
|
-
"datetime_duration_to_milliseconds",
|
1360
1094
|
"datetime_duration_to_timedelta",
|
1361
1095
|
"datetime_utc",
|
1362
|
-
"days_since_epoch",
|
1363
|
-
"days_since_epoch_to_date",
|
1364
1096
|
"ensure_month",
|
1365
|
-
"format_datetime_local_and_utc",
|
1366
1097
|
"get_date",
|
1367
1098
|
"get_datetime",
|
1368
1099
|
"get_half_years",
|
@@ -1373,27 +1104,19 @@ __all__ = [
|
|
1373
1104
|
"get_today",
|
1374
1105
|
"get_years",
|
1375
1106
|
"is_integral_timedelta",
|
1376
|
-
"is_plain_datetime",
|
1377
1107
|
"is_weekday",
|
1378
1108
|
"is_zero_time",
|
1379
|
-
"is_zoned_datetime",
|
1380
1109
|
"maybe_sub_pct_y",
|
1381
1110
|
"mean_datetime",
|
1382
1111
|
"mean_timedelta",
|
1383
1112
|
"microseconds_since_epoch",
|
1384
1113
|
"microseconds_since_epoch_to_datetime",
|
1385
1114
|
"microseconds_to_timedelta",
|
1386
|
-
"milliseconds_since_epoch",
|
1387
|
-
"milliseconds_since_epoch_to_datetime",
|
1388
|
-
"milliseconds_to_timedelta",
|
1389
|
-
"parse_date_compact",
|
1390
|
-
"parse_datetime_compact",
|
1391
1115
|
"parse_month",
|
1392
1116
|
"parse_two_digit_year",
|
1393
1117
|
"round_datetime",
|
1394
1118
|
"round_to_next_weekday",
|
1395
1119
|
"round_to_prev_weekday",
|
1396
|
-
"serialize_compact",
|
1397
1120
|
"serialize_month",
|
1398
1121
|
"sub_duration",
|
1399
1122
|
"timedelta_since_epoch",
|
utilities/iterables.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import builtins
|
4
|
-
import datetime as dt
|
5
4
|
from collections import Counter
|
6
5
|
from collections.abc import (
|
7
6
|
Callable,
|
@@ -34,7 +33,7 @@ from typing import (
|
|
34
33
|
)
|
35
34
|
|
36
35
|
from utilities.errors import ImpossibleCaseError
|
37
|
-
from utilities.functions import ensure_hashable,
|
36
|
+
from utilities.functions import ensure_hashable, ensure_str
|
38
37
|
from utilities.math import (
|
39
38
|
_CheckIntegerEqualError,
|
40
39
|
_CheckIntegerEqualOrApproxError,
|
@@ -45,7 +44,6 @@ from utilities.math import (
|
|
45
44
|
from utilities.reprlib import get_repr
|
46
45
|
from utilities.sentinel import Sentinel, sentinel
|
47
46
|
from utilities.types import Sign, THashable, TSupportsAdd, TSupportsLT
|
48
|
-
from utilities.zoneinfo import UTC
|
49
47
|
|
50
48
|
if TYPE_CHECKING:
|
51
49
|
from types import NoneType
|
@@ -1326,9 +1324,6 @@ def _sort_iterable_cmp(x: Any, y: Any, /) -> Sign:
|
|
1326
1324
|
if x is None:
|
1327
1325
|
y = cast("NoneType", y)
|
1328
1326
|
return 0
|
1329
|
-
if isinstance(x, dt.datetime):
|
1330
|
-
y = cast("dt.datetime", y)
|
1331
|
-
return _sort_iterable_cmp_datetimes(x, y)
|
1332
1327
|
if isinstance(x, float):
|
1333
1328
|
y = cast("float", y)
|
1334
1329
|
return _sort_iterable_cmp_floats(x, y)
|
@@ -1371,30 +1366,6 @@ class SortIterableError(Exception):
|
|
1371
1366
|
return f"Unable to sort {get_repr(self.x)} and {get_repr(self.y)}"
|
1372
1367
|
|
1373
1368
|
|
1374
|
-
def _sort_iterable_cmp_datetimes(x: dt.datetime, y: dt.datetime, /) -> Sign:
|
1375
|
-
"""Compare two datetimes."""
|
1376
|
-
match x.tzinfo, y.tzinfo:
|
1377
|
-
case None, None:
|
1378
|
-
return cast("Sign", (x > y) - (x < y))
|
1379
|
-
case dt.tzinfo(), None:
|
1380
|
-
return 1
|
1381
|
-
case None, dt.tzinfo():
|
1382
|
-
return -1
|
1383
|
-
case dt.tzinfo(), dt.tzinfo():
|
1384
|
-
x_utc = x.astimezone(tz=UTC)
|
1385
|
-
y_utc = y.astimezone(tz=UTC)
|
1386
|
-
result = cast("Sign", (x_utc > y_utc) - (x_utc < y_utc))
|
1387
|
-
if result != 0:
|
1388
|
-
return result
|
1389
|
-
x_time_zone = ensure_not_none(ensure_not_none(x.tzinfo).tzname(x))
|
1390
|
-
y_time_zone = ensure_not_none(ensure_not_none(y.tzinfo).tzname(y))
|
1391
|
-
return cast(
|
1392
|
-
"Sign", (x_time_zone > y_time_zone) - (x_time_zone < y_time_zone)
|
1393
|
-
)
|
1394
|
-
case _ as never:
|
1395
|
-
assert_never(never)
|
1396
|
-
|
1397
|
-
|
1398
1369
|
def _sort_iterable_cmp_floats(x: float, y: float, /) -> Sign:
|
1399
1370
|
"""Compare two floats."""
|
1400
1371
|
x_nan, y_nan = map(isnan, [x, y])
|
utilities/operator.py
CHANGED
@@ -1,23 +1,17 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import datetime as dt
|
4
3
|
from collections.abc import Callable, Mapping, Sequence
|
5
4
|
from collections.abc import Set as AbstractSet
|
6
5
|
from dataclasses import asdict, dataclass
|
7
6
|
from typing import TYPE_CHECKING, Any, TypeVar, cast, override
|
8
7
|
|
9
8
|
import utilities.math
|
10
|
-
from utilities.datetime import (
|
11
|
-
AreEqualDatesOrDateTimesError,
|
12
|
-
AreEqualDateTimesError,
|
13
|
-
are_equal_dates_or_datetimes,
|
14
|
-
)
|
15
9
|
from utilities.functions import is_dataclass_instance
|
16
10
|
from utilities.iterables import SortIterableError, sort_iterable
|
17
11
|
from utilities.reprlib import get_repr
|
18
12
|
|
19
13
|
if TYPE_CHECKING:
|
20
|
-
from utilities.types import Dataclass,
|
14
|
+
from utilities.types import Dataclass, Number
|
21
15
|
|
22
16
|
_T = TypeVar("_T")
|
23
17
|
|
@@ -51,12 +45,6 @@ def is_equal(
|
|
51
45
|
if isinstance(x, str): # else Sequence
|
52
46
|
y = cast("str", y)
|
53
47
|
return x == y
|
54
|
-
if isinstance(x, dt.date | dt.datetime):
|
55
|
-
y = cast("DateOrDateTime", y)
|
56
|
-
try:
|
57
|
-
return are_equal_dates_or_datetimes(x, y)
|
58
|
-
except (AreEqualDateTimesError, AreEqualDatesOrDateTimesError):
|
59
|
-
return False
|
60
48
|
if is_dataclass_instance(x):
|
61
49
|
y = cast("Dataclass", y)
|
62
50
|
x_values = asdict(x)
|
@@ -80,8 +68,26 @@ def is_equal(
|
|
80
68
|
try:
|
81
69
|
x_sorted = sort_iterable(x)
|
82
70
|
y_sorted = sort_iterable(y)
|
83
|
-
except SortIterableError
|
84
|
-
|
71
|
+
except SortIterableError:
|
72
|
+
x_in_y = all(
|
73
|
+
any(
|
74
|
+
is_equal(
|
75
|
+
x_i, y_i, rel_tol=rel_tol, abs_tol=abs_tol, extra=extra
|
76
|
+
)
|
77
|
+
for y_i in y
|
78
|
+
)
|
79
|
+
for x_i in x
|
80
|
+
)
|
81
|
+
y_in_x = all(
|
82
|
+
any(
|
83
|
+
is_equal(
|
84
|
+
x_i, y_i, rel_tol=rel_tol, abs_tol=abs_tol, extra=extra
|
85
|
+
)
|
86
|
+
for x_i in x
|
87
|
+
)
|
88
|
+
for y_i in y
|
89
|
+
)
|
90
|
+
return x_in_y and y_in_x
|
85
91
|
return is_equal(x_sorted, y_sorted, rel_tol=rel_tol, abs_tol=abs_tol)
|
86
92
|
if isinstance(x, Sequence):
|
87
93
|
y = cast("Sequence[Any]", y)
|