dycw-utilities 0.131.0__py3-none-any.whl → 0.131.2__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.0.dist-info → dycw_utilities-0.131.2.dist-info}/METADATA +1 -1
- {dycw_utilities-0.131.0.dist-info → dycw_utilities-0.131.2.dist-info}/RECORD +11 -10
- utilities/__init__.py +1 -1
- utilities/hypothesis.py +162 -51
- utilities/logging.py +1 -1
- utilities/sqlalchemy_polars.py +1 -17
- utilities/typing.py +1 -1
- utilities/whenever.py +1 -64
- utilities/whenever2.py +139 -0
- {dycw_utilities-0.131.0.dist-info → dycw_utilities-0.131.2.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.131.0.dist-info → dycw_utilities-0.131.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=ajN78bRkB5Im8iLJo5SoqSMwaBs05vhc-hxvF77jai4,60
|
2
2
|
utilities/aiolimiter.py,sha256=mD0wEiqMgwpty4XTbawFpnkkmJS6R4JRsVXFUaoitSU,628
|
3
3
|
utilities/altair.py,sha256=Gpja-flOo-Db0PIPJLJsgzAlXWoKUjPU1qY-DQ829ek,9156
|
4
4
|
utilities/asyncio.py,sha256=lvdgBhuMtxq0dpiwF9g2WMMrit3kqXibN1V5NZ4xdbo,38046
|
@@ -24,7 +24,7 @@ utilities/getpass.py,sha256=DfN5UgMAtFCqS3dSfFHUfqIMZX2shXvwphOz_6J6f6A,103
|
|
24
24
|
utilities/git.py,sha256=oi7-_l5e9haSANSCvQw25ufYGoNahuUPHAZ6114s3JQ,1191
|
25
25
|
utilities/hashlib.py,sha256=SVTgtguur0P4elppvzOBbLEjVM3Pea0eWB61yg2ilxo,309
|
26
26
|
utilities/http.py,sha256=WcahTcKYRtZ04WXQoWt5EGCgFPcyHD3EJdlMfxvDt-0,946
|
27
|
-
utilities/hypothesis.py,sha256=
|
27
|
+
utilities/hypothesis.py,sha256=jiFJsS6rg4273BYDjrHT1iYH7D7ybROnH5bca9rBWqI,47372
|
28
28
|
utilities/importlib.py,sha256=mV1xT_O_zt_GnZZ36tl3xOmMaN_3jErDWY54fX39F6Y,429
|
29
29
|
utilities/inflect.py,sha256=DbqB5Q9FbRGJ1NbvEiZBirRMxCxgrz91zy5jCO9ZIs0,347
|
30
30
|
utilities/ipython.py,sha256=V2oMYHvEKvlNBzxDXdLvKi48oUq2SclRg5xasjaXStw,763
|
@@ -32,7 +32,7 @@ utilities/iterables.py,sha256=mDqw2_0MUVp-P8FklgcaVTi2TXduH0MxbhTDzzhSBho,44915
|
|
32
32
|
utilities/jupyter.py,sha256=ft5JA7fBxXKzP-L9W8f2-wbF0QeYc_2uLQNFDVk4Z-M,2917
|
33
33
|
utilities/libcst.py,sha256=Jto5ppzRzsxn4AD32IS8n0lbgLYXwsVJB6EY8giNZyY,4974
|
34
34
|
utilities/lightweight_charts.py,sha256=0xNfcsrgFI0R9xL25LtSm-W5yhfBI93qQNT6HyaXAhg,2769
|
35
|
-
utilities/logging.py,sha256=
|
35
|
+
utilities/logging.py,sha256=DoLjy18w87fu6xDIBwiCtx3sAsNobm1QqQ4e2RRmp50,18421
|
36
36
|
utilities/luigi.py,sha256=fpH9MbxJDuo6-k9iCXRayFRtiVbUtibCJKugf7ygpv0,5988
|
37
37
|
utilities/math.py,sha256=-mQgbah-dPJwOEWf3SonrFoVZ2AVxMgpeQ3dfVa-oJA,26764
|
38
38
|
utilities/memory_profiler.py,sha256=tf2C51P2lCujPGvRt2Rfc7VEw5LDXmVPCG3z_AvBmbU,962
|
@@ -70,7 +70,7 @@ utilities/shelve.py,sha256=HZsMwK4tcIfg3sh0gApx4-yjQnrY4o3V3ZRimvRhoW0,738
|
|
70
70
|
utilities/slack_sdk.py,sha256=ltmzv68aa73CJGqTDvt8L9vDm22YU9iOCo3NCiNd3vA,4347
|
71
71
|
utilities/socket.py,sha256=K77vfREvzoVTrpYKo6MZakol0EYu2q1sWJnnZqL0So0,118
|
72
72
|
utilities/sqlalchemy.py,sha256=IuQ7CIVNl29TG6i81K6fam8NmTmPjtA6OiIN4nIM9W8,37935
|
73
|
-
utilities/sqlalchemy_polars.py,sha256=
|
73
|
+
utilities/sqlalchemy_polars.py,sha256=hApbjQUY-XgKfAXcun8gDP2lGh5LxrudnCpbG_hrYa0,14968
|
74
74
|
utilities/statsmodels.py,sha256=koyiBHvpMcSiBfh99wFUfSggLNx7cuAw3rwyfAhoKpQ,3410
|
75
75
|
utilities/streamlit.py,sha256=U9PJBaKP1IdSykKhPZhIzSPTZsmLsnwbEPZWzNhJPKk,2955
|
76
76
|
utilities/string.py,sha256=XmU-s04qIV_tODnKl2pQiwmHaxzgOqRKU-RyzdrfvSE,620
|
@@ -81,16 +81,17 @@ utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
|
|
81
81
|
utilities/timer.py,sha256=Rkc49KSpHuC8s7vUxGO9DU55U9I6yDKnchsQqrUCVBs,4075
|
82
82
|
utilities/traceback.py,sha256=k3QhUca-643rl11S9uhibfNPlxjavOboCK56036KRcE,8859
|
83
83
|
utilities/types.py,sha256=gP04CcCOyFrG7BgblVCsrrChiuO2x842NDVW-GF7odo,18370
|
84
|
-
utilities/typing.py,sha256=
|
84
|
+
utilities/typing.py,sha256=kQWywPcRbFBKmvQBELmgbiqSHsnlo_D0ru53vl6KDeY,13846
|
85
85
|
utilities/tzdata.py,sha256=yCf70NICwAeazN3_JcXhWvRqCy06XJNQ42j7r6gw3HY,1217
|
86
86
|
utilities/tzlocal.py,sha256=3upDNFBvGh1l9njmLR2z2S6K6VxQSb7QizYGUbAH3JU,960
|
87
87
|
utilities/uuid.py,sha256=jJTFxz-CWgltqNuzmythB7iEQ-Q1mCwPevUfKthZT3c,611
|
88
88
|
utilities/version.py,sha256=ufhJMmI6KPs1-3wBI71aj5wCukd3sP_m11usLe88DNA,5117
|
89
89
|
utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
|
90
|
-
utilities/whenever.py,sha256=
|
90
|
+
utilities/whenever.py,sha256=jS31ZAY5OMxFxLja_Yo5Fidi87Pd-GoVZ7Vi_teqVDA,16743
|
91
|
+
utilities/whenever2.py,sha256=uub90yQg2lUC8at7lnGR30qt5iuNlqPPTePwiKckhOE,3994
|
91
92
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
92
93
|
utilities/zoneinfo.py,sha256=-5j7IQ9nb7gR43rdgA7ms05im-XuqhAk9EJnQBXxCoQ,1874
|
93
|
-
dycw_utilities-0.131.
|
94
|
-
dycw_utilities-0.131.
|
95
|
-
dycw_utilities-0.131.
|
96
|
-
dycw_utilities-0.131.
|
94
|
+
dycw_utilities-0.131.2.dist-info/METADATA,sha256=iP5y9JBuywFkkZML9iGcqagto8XgWUjD8sY4zCMWJv8,12989
|
95
|
+
dycw_utilities-0.131.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
96
|
+
dycw_utilities-0.131.2.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
97
|
+
dycw_utilities-0.131.2.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/hypothesis.py
CHANGED
@@ -47,6 +47,7 @@ from hypothesis.strategies import (
|
|
47
47
|
uuids,
|
48
48
|
)
|
49
49
|
from hypothesis.utils.conventions import not_set
|
50
|
+
from whenever import Date, DateDelta
|
50
51
|
|
51
52
|
from utilities.datetime import (
|
52
53
|
DATETIME_MAX_NAIVE,
|
@@ -88,7 +89,7 @@ from utilities.platform import IS_WINDOWS
|
|
88
89
|
from utilities.sentinel import Sentinel, sentinel
|
89
90
|
from utilities.tempfile import TEMP_DIR, TemporaryDirectory
|
90
91
|
from utilities.version import Version
|
91
|
-
from utilities.zoneinfo import UTC
|
92
|
+
from utilities.zoneinfo import UTC, ensure_time_zone
|
92
93
|
|
93
94
|
if TYPE_CHECKING:
|
94
95
|
from collections.abc import Collection, Hashable, Iterable, Iterator, Sequence
|
@@ -96,11 +97,10 @@ if TYPE_CHECKING:
|
|
96
97
|
|
97
98
|
from hypothesis.database import ExampleDatabase
|
98
99
|
from numpy.random import RandomState
|
99
|
-
from
|
100
|
+
from whenever import PlainDateTime, ZonedDateTime
|
100
101
|
|
101
102
|
from utilities.numpy import NDArrayB, NDArrayF, NDArrayI, NDArrayO
|
102
|
-
from utilities.
|
103
|
-
from utilities.types import Duration, Number, RoundMode
|
103
|
+
from utilities.types import Duration, Number, RoundMode, TimeZoneLike
|
104
104
|
|
105
105
|
|
106
106
|
_T = TypeVar("_T")
|
@@ -160,6 +160,45 @@ def bool_arrays(
|
|
160
160
|
##
|
161
161
|
|
162
162
|
|
163
|
+
@composite
|
164
|
+
def date_deltas_whenever(
|
165
|
+
draw: DrawFn,
|
166
|
+
/,
|
167
|
+
*,
|
168
|
+
min_value: MaybeSearchStrategy[DateDelta | None] = None,
|
169
|
+
max_value: MaybeSearchStrategy[DateDelta | None] = None,
|
170
|
+
) -> DateDelta:
|
171
|
+
"""Strategy for generating date deltas."""
|
172
|
+
from utilities.whenever2 import DATE_DELTA_MAX, DATE_DELTA_MIN
|
173
|
+
|
174
|
+
min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
|
175
|
+
match min_value_:
|
176
|
+
case None:
|
177
|
+
min_value_ = DATE_DELTA_MIN
|
178
|
+
case DateDelta():
|
179
|
+
...
|
180
|
+
case _ as never:
|
181
|
+
assert_never(never)
|
182
|
+
match max_value_:
|
183
|
+
case None:
|
184
|
+
max_value_ = DATE_DELTA_MAX
|
185
|
+
case DateDelta():
|
186
|
+
...
|
187
|
+
case _ as never:
|
188
|
+
assert_never(never)
|
189
|
+
min_years, min_months, min_days = min_value_.in_years_months_days()
|
190
|
+
assert min_years == 0
|
191
|
+
assert min_months == 0
|
192
|
+
max_years, max_months, max_days = max_value_.in_years_months_days()
|
193
|
+
assert max_years == 0
|
194
|
+
assert max_months == 0
|
195
|
+
days = draw(integers(min_value=min_days, max_value=max_days))
|
196
|
+
return DateDelta(days=days)
|
197
|
+
|
198
|
+
|
199
|
+
##
|
200
|
+
|
201
|
+
|
163
202
|
@composite
|
164
203
|
def date_durations(
|
165
204
|
draw: DrawFn,
|
@@ -240,6 +279,41 @@ def dates_two_digit_year(
|
|
240
279
|
##
|
241
280
|
|
242
281
|
|
282
|
+
@composite
|
283
|
+
def dates_whenever(
|
284
|
+
draw: DrawFn,
|
285
|
+
/,
|
286
|
+
*,
|
287
|
+
min_value: MaybeSearchStrategy[Date | None] = None,
|
288
|
+
max_value: MaybeSearchStrategy[Date | None] = None,
|
289
|
+
) -> Date:
|
290
|
+
"""Strategy for generating dates."""
|
291
|
+
from utilities.whenever2 import DATE_MAX, DATE_MIN
|
292
|
+
|
293
|
+
min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
|
294
|
+
match min_value_:
|
295
|
+
case None:
|
296
|
+
min_value_ = DATE_MIN
|
297
|
+
case Date():
|
298
|
+
...
|
299
|
+
case _ as never:
|
300
|
+
assert_never(never)
|
301
|
+
match max_value_:
|
302
|
+
case None:
|
303
|
+
max_value_ = DATE_MAX
|
304
|
+
case Date():
|
305
|
+
...
|
306
|
+
case _ as never:
|
307
|
+
assert_never(never)
|
308
|
+
py_date = draw(
|
309
|
+
dates(min_value=min_value_.py_date(), max_value=max_value_.py_date())
|
310
|
+
)
|
311
|
+
return Date.from_py_date(py_date)
|
312
|
+
|
313
|
+
|
314
|
+
##
|
315
|
+
|
316
|
+
|
243
317
|
@composite
|
244
318
|
def datetime_durations(
|
245
319
|
draw: DrawFn,
|
@@ -922,7 +996,7 @@ def _pairs_map(elements: list[_T], /) -> tuple[_T, _T]:
|
|
922
996
|
|
923
997
|
def paths() -> SearchStrategy[Path]:
|
924
998
|
"""Strategy for generating `Path`s."""
|
925
|
-
reserved = {"NUL"}
|
999
|
+
reserved = {"AUX", "NUL"}
|
926
1000
|
strategy = text_ascii(min_size=1, max_size=10).filter(lambda x: x not in reserved)
|
927
1001
|
return lists(strategy, max_size=10).map(lambda parts: Path(*parts))
|
928
1002
|
|
@@ -967,6 +1041,45 @@ class PlainDateTimesError(Exception):
|
|
967
1041
|
##
|
968
1042
|
|
969
1043
|
|
1044
|
+
@composite
|
1045
|
+
def plain_datetimes_whenever(
|
1046
|
+
draw: DrawFn,
|
1047
|
+
/,
|
1048
|
+
*,
|
1049
|
+
min_value: MaybeSearchStrategy[PlainDateTime | None] = None,
|
1050
|
+
max_value: MaybeSearchStrategy[PlainDateTime | None] = None,
|
1051
|
+
) -> PlainDateTime:
|
1052
|
+
"""Strategy for generating plain datetimes."""
|
1053
|
+
from whenever import PlainDateTime
|
1054
|
+
|
1055
|
+
from utilities.whenever2 import PLAIN_DATE_TIME_MAX, PLAIN_DATE_TIME_MIN
|
1056
|
+
|
1057
|
+
min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
|
1058
|
+
match min_value_:
|
1059
|
+
case None:
|
1060
|
+
min_value_ = PLAIN_DATE_TIME_MIN
|
1061
|
+
case PlainDateTime():
|
1062
|
+
...
|
1063
|
+
case _ as never:
|
1064
|
+
assert_never(never)
|
1065
|
+
match max_value_:
|
1066
|
+
case None:
|
1067
|
+
max_value_ = PLAIN_DATE_TIME_MAX
|
1068
|
+
case PlainDateTime():
|
1069
|
+
...
|
1070
|
+
case _ as never:
|
1071
|
+
assert_never(never)
|
1072
|
+
py_datetime = draw(
|
1073
|
+
datetimes(
|
1074
|
+
min_value=min_value_.py_datetime(), max_value=max_value_.py_datetime()
|
1075
|
+
)
|
1076
|
+
)
|
1077
|
+
return PlainDateTime.from_py_datetime(py_datetime)
|
1078
|
+
|
1079
|
+
|
1080
|
+
##
|
1081
|
+
|
1082
|
+
|
970
1083
|
@composite
|
971
1084
|
def random_states(
|
972
1085
|
draw: DrawFn, /, *, seed: MaybeSearchStrategy[int | None] = None
|
@@ -1117,51 +1230,6 @@ def slices(
|
|
1117
1230
|
##
|
1118
1231
|
|
1119
1232
|
|
1120
|
-
_STRATEGY_DIALECTS: list[Dialect] = ["sqlite", "postgresql"]
|
1121
|
-
_SQLALCHEMY_ENGINE_DIALECTS = sampled_from(_STRATEGY_DIALECTS)
|
1122
|
-
|
1123
|
-
|
1124
|
-
async def sqlalchemy_engines(
|
1125
|
-
data: DataObject,
|
1126
|
-
/,
|
1127
|
-
*tables_or_orms: TableOrORMInstOrClass,
|
1128
|
-
dialect: MaybeSearchStrategy[Dialect] = _SQLALCHEMY_ENGINE_DIALECTS,
|
1129
|
-
) -> AsyncEngine:
|
1130
|
-
"""Strategy for generating sqlalchemy engines."""
|
1131
|
-
from utilities.sqlalchemy import create_async_engine
|
1132
|
-
|
1133
|
-
dialect_: Dialect = draw2(data, dialect)
|
1134
|
-
if "CI" in environ: # pragma: no cover
|
1135
|
-
_ = assume(dialect_ == "sqlite")
|
1136
|
-
match dialect_:
|
1137
|
-
case "sqlite":
|
1138
|
-
temp_path = data.draw(temp_paths())
|
1139
|
-
path = Path(temp_path, "db.sqlite")
|
1140
|
-
engine = create_async_engine("sqlite+aiosqlite", database=str(path))
|
1141
|
-
|
1142
|
-
class EngineWithPath(type(engine)): ...
|
1143
|
-
|
1144
|
-
engine_with_path = EngineWithPath(engine.sync_engine)
|
1145
|
-
cast(
|
1146
|
-
"Any", engine_with_path
|
1147
|
-
).temp_path = temp_path # keep `temp_path` alive
|
1148
|
-
return engine_with_path
|
1149
|
-
case "postgresql": # skipif-ci-and-not-linux
|
1150
|
-
from utilities.sqlalchemy import ensure_tables_dropped
|
1151
|
-
|
1152
|
-
engine = create_async_engine(
|
1153
|
-
"postgresql+asyncpg", host="localhost", port=5432, database="testing"
|
1154
|
-
)
|
1155
|
-
with assume_does_not_raise(ConnectionRefusedError):
|
1156
|
-
await ensure_tables_dropped(engine, *tables_or_orms)
|
1157
|
-
return engine
|
1158
|
-
case _: # pragma: no cover
|
1159
|
-
raise NotImplementedError(dialect)
|
1160
|
-
|
1161
|
-
|
1162
|
-
##
|
1163
|
-
|
1164
|
-
|
1165
1233
|
@composite
|
1166
1234
|
def str_arrays(
|
1167
1235
|
draw: DrawFn,
|
@@ -1474,6 +1542,46 @@ class ZonedDateTimesError(Exception):
|
|
1474
1542
|
return "Rounding requires a timedelta; got None"
|
1475
1543
|
|
1476
1544
|
|
1545
|
+
##
|
1546
|
+
|
1547
|
+
|
1548
|
+
@composite
|
1549
|
+
def zoned_datetimes_whenever(
|
1550
|
+
draw: DrawFn,
|
1551
|
+
/,
|
1552
|
+
*,
|
1553
|
+
min_value: MaybeSearchStrategy[PlainDateTime | ZonedDateTime | None] = None,
|
1554
|
+
max_value: MaybeSearchStrategy[PlainDateTime | ZonedDateTime | None] = None,
|
1555
|
+
time_zone: MaybeSearchStrategy[TimeZoneLike] = UTC,
|
1556
|
+
) -> ZonedDateTime:
|
1557
|
+
"""Strategy for generating zoned datetimes."""
|
1558
|
+
from whenever import PlainDateTime, ZonedDateTime
|
1559
|
+
|
1560
|
+
min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
|
1561
|
+
time_zone_ = ensure_time_zone(draw2(draw, time_zone))
|
1562
|
+
match min_value_:
|
1563
|
+
case None | PlainDateTime():
|
1564
|
+
...
|
1565
|
+
case ZonedDateTime():
|
1566
|
+
with assume_does_not_raise(ValueError):
|
1567
|
+
min_value_ = min_value_.to_tz(time_zone_.key).to_plain()
|
1568
|
+
case _ as never:
|
1569
|
+
assert_never(never)
|
1570
|
+
match max_value_:
|
1571
|
+
case None | PlainDateTime():
|
1572
|
+
...
|
1573
|
+
case ZonedDateTime():
|
1574
|
+
with assume_does_not_raise(ValueError):
|
1575
|
+
max_value_ = max_value_.to_tz(time_zone_.key).to_plain()
|
1576
|
+
case _ as never:
|
1577
|
+
assert_never(never)
|
1578
|
+
plain_datetime = draw(
|
1579
|
+
plain_datetimes_whenever(min_value=min_value_, max_value=max_value_)
|
1580
|
+
)
|
1581
|
+
with assume_does_not_raise(ValueError):
|
1582
|
+
return plain_datetime.assume_tz(time_zone_.key, disambiguate="raise")
|
1583
|
+
|
1584
|
+
|
1477
1585
|
__all__ = [
|
1478
1586
|
"Draw2Error",
|
1479
1587
|
"MaybeSearchStrategy",
|
@@ -1482,8 +1590,10 @@ __all__ = [
|
|
1482
1590
|
"ZonedDateTimesError",
|
1483
1591
|
"assume_does_not_raise",
|
1484
1592
|
"bool_arrays",
|
1593
|
+
"date_deltas_whenever",
|
1485
1594
|
"date_durations",
|
1486
1595
|
"dates_two_digit_year",
|
1596
|
+
"dates_whenever",
|
1487
1597
|
"datetime_durations",
|
1488
1598
|
"draw2",
|
1489
1599
|
"float32s",
|
@@ -1507,12 +1617,12 @@ __all__ = [
|
|
1507
1617
|
"paths",
|
1508
1618
|
"plain_datetimes",
|
1509
1619
|
"plain_datetimes",
|
1620
|
+
"plain_datetimes_whenever",
|
1510
1621
|
"random_states",
|
1511
1622
|
"sentinels",
|
1512
1623
|
"sets_fixed_length",
|
1513
1624
|
"setup_hypothesis_profiles",
|
1514
1625
|
"slices",
|
1515
|
-
"sqlalchemy_engines",
|
1516
1626
|
"str_arrays",
|
1517
1627
|
"temp_dirs",
|
1518
1628
|
"temp_paths",
|
@@ -1528,4 +1638,5 @@ __all__ = [
|
|
1528
1638
|
"uint64s",
|
1529
1639
|
"versions",
|
1530
1640
|
"zoned_datetimes",
|
1641
|
+
"zoned_datetimes_whenever",
|
1531
1642
|
]
|
utilities/logging.py
CHANGED
@@ -190,7 +190,7 @@ def get_formatter(
|
|
190
190
|
) -> Formatter:
|
191
191
|
"""Get the formatter; colored if available."""
|
192
192
|
if whenever:
|
193
|
-
from utilities.
|
193
|
+
from utilities.whenever2 import WheneverLogRecord
|
194
194
|
|
195
195
|
setLogRecordFactory(WheneverLogRecord)
|
196
196
|
format_ = format_.replace("{asctime}", "{zoned_datetime}")
|
utilities/sqlalchemy_polars.py
CHANGED
@@ -25,7 +25,6 @@ from polars import (
|
|
25
25
|
)
|
26
26
|
from sqlalchemy import Column, Select, select
|
27
27
|
from sqlalchemy.exc import DuplicateColumnError
|
28
|
-
from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
|
29
28
|
|
30
29
|
from utilities.asyncio import timeout_dur
|
31
30
|
from utilities.functions import identity
|
@@ -61,6 +60,7 @@ if TYPE_CHECKING:
|
|
61
60
|
)
|
62
61
|
|
63
62
|
from polars._typing import PolarsDataType, SchemaDict
|
63
|
+
from sqlalchemy.ext.asyncio import AsyncEngine
|
64
64
|
from sqlalchemy.sql import ColumnCollection
|
65
65
|
from sqlalchemy.sql.base import ReadOnlyColumnCollection
|
66
66
|
from tenacity.retry import RetryBaseT
|
@@ -307,22 +307,6 @@ async def select_to_dataframe(
|
|
307
307
|
**kwargs: Any,
|
308
308
|
) -> DataFrame | Iterable[DataFrame] | AsyncIterable[DataFrame]:
|
309
309
|
"""Read a table from a database into a DataFrame."""
|
310
|
-
if not issubclass(AsyncEngine, type(engine)):
|
311
|
-
# for handling testing
|
312
|
-
engine = create_async_engine(engine.url)
|
313
|
-
return await select_to_dataframe(
|
314
|
-
sel,
|
315
|
-
engine,
|
316
|
-
snake=snake,
|
317
|
-
time_zone=time_zone,
|
318
|
-
batch_size=batch_size,
|
319
|
-
in_clauses=in_clauses,
|
320
|
-
in_clauses_chunk_size=in_clauses_chunk_size,
|
321
|
-
chunk_size_frac=chunk_size_frac,
|
322
|
-
timeout=timeout,
|
323
|
-
error=error,
|
324
|
-
**kwargs,
|
325
|
-
)
|
326
310
|
if snake:
|
327
311
|
sel = _select_to_dataframe_apply_snake(sel)
|
328
312
|
schema = _select_to_dataframe_map_select_to_df_schema(sel, time_zone=time_zone)
|
utilities/typing.py
CHANGED
@@ -234,7 +234,7 @@ def is_instance_gen(obj: Any, type_: Any, /) -> bool:
|
|
234
234
|
"""Check if an instance relationship holds, except bool<int."""
|
235
235
|
# parent
|
236
236
|
if isinstance(type_, tuple):
|
237
|
-
return any(is_instance_gen(obj, t) for t in type_)
|
237
|
+
return any(is_instance_gen(obj, t) for t in type_) # skipif-ci-and-not-windows
|
238
238
|
if is_literal_type(type_):
|
239
239
|
return obj in get_args(type_)
|
240
240
|
if is_union_type(type_):
|
utilities/whenever.py
CHANGED
@@ -4,9 +4,7 @@ import datetime as dt
|
|
4
4
|
import re
|
5
5
|
from contextlib import suppress
|
6
6
|
from dataclasses import dataclass
|
7
|
-
from
|
8
|
-
from logging import LogRecord
|
9
|
-
from typing import TYPE_CHECKING, Any, override
|
7
|
+
from typing import TYPE_CHECKING, override
|
10
8
|
|
11
9
|
from whenever import (
|
12
10
|
Date,
|
@@ -35,8 +33,6 @@ from utilities.re import (
|
|
35
33
|
from utilities.zoneinfo import UTC, ensure_time_zone, get_time_zone_name
|
36
34
|
|
37
35
|
if TYPE_CHECKING:
|
38
|
-
from zoneinfo import ZoneInfo
|
39
|
-
|
40
36
|
from utilities.types import (
|
41
37
|
DateLike,
|
42
38
|
DateTimeLike,
|
@@ -565,64 +561,6 @@ class SerializeZonedDateTimeError(Exception):
|
|
565
561
|
##
|
566
562
|
|
567
563
|
|
568
|
-
class WheneverLogRecord(LogRecord):
|
569
|
-
"""Log record powered by `whenever`."""
|
570
|
-
|
571
|
-
zoned_datetime: str
|
572
|
-
|
573
|
-
@override
|
574
|
-
def __init__(
|
575
|
-
self,
|
576
|
-
name: str,
|
577
|
-
level: int,
|
578
|
-
pathname: str,
|
579
|
-
lineno: int,
|
580
|
-
msg: object,
|
581
|
-
args: Any,
|
582
|
-
exc_info: Any,
|
583
|
-
func: str | None = None,
|
584
|
-
sinfo: str | None = None,
|
585
|
-
) -> None:
|
586
|
-
super().__init__(
|
587
|
-
name, level, pathname, lineno, msg, args, exc_info, func, sinfo
|
588
|
-
)
|
589
|
-
length = self._get_length()
|
590
|
-
plain = format(self._get_now().to_plain().format_common_iso(), f"{length}s")
|
591
|
-
time_zone = self._get_time_zone_key()
|
592
|
-
self.zoned_datetime = f"{plain}[{time_zone}]"
|
593
|
-
|
594
|
-
@classmethod
|
595
|
-
@cache
|
596
|
-
def _get_time_zone(cls) -> ZoneInfo:
|
597
|
-
"""Get the local timezone."""
|
598
|
-
try:
|
599
|
-
from utilities.tzlocal import get_local_time_zone
|
600
|
-
except ModuleNotFoundError: # pragma: no cover
|
601
|
-
return UTC
|
602
|
-
return get_local_time_zone()
|
603
|
-
|
604
|
-
@classmethod
|
605
|
-
@cache
|
606
|
-
def _get_time_zone_key(cls) -> str:
|
607
|
-
"""Get the local timezone as a string."""
|
608
|
-
return cls._get_time_zone().key
|
609
|
-
|
610
|
-
@classmethod
|
611
|
-
@cache
|
612
|
-
def _get_length(cls) -> int:
|
613
|
-
"""Get maximum length of a formatted string."""
|
614
|
-
now = cls._get_now().replace(nanosecond=1000).to_plain()
|
615
|
-
return len(now.format_common_iso())
|
616
|
-
|
617
|
-
@classmethod
|
618
|
-
def _get_now(cls) -> ZonedDateTime:
|
619
|
-
"""Get the current zoned datetime."""
|
620
|
-
return ZonedDateTime.now(cls._get_time_zone().key)
|
621
|
-
|
622
|
-
|
623
|
-
##
|
624
|
-
|
625
|
-
|
626
564
|
def _to_datetime_delta(timedelta: dt.timedelta, /) -> DateTimeDelta:
|
627
565
|
"""Serialize a timedelta."""
|
628
566
|
total_microseconds = datetime_duration_to_microseconds(timedelta)
|
@@ -672,7 +610,6 @@ __all__ = [
|
|
672
610
|
"SerializePlainDateTimeError",
|
673
611
|
"SerializeTimeDeltaError",
|
674
612
|
"SerializeZonedDateTimeError",
|
675
|
-
"WheneverLogRecord",
|
676
613
|
"check_valid_zoned_datetime",
|
677
614
|
"ensure_date",
|
678
615
|
"ensure_datetime",
|
utilities/whenever2.py
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import datetime as dt
|
4
|
+
from functools import cache
|
5
|
+
from logging import LogRecord
|
6
|
+
from typing import TYPE_CHECKING, Any, override
|
7
|
+
|
8
|
+
from whenever import Date, DateTimeDelta, PlainDateTime, ZonedDateTime
|
9
|
+
|
10
|
+
from utilities.zoneinfo import UTC, get_time_zone_name
|
11
|
+
|
12
|
+
if TYPE_CHECKING:
|
13
|
+
from zoneinfo import ZoneInfo
|
14
|
+
|
15
|
+
from utilities.types import TimeZoneLike
|
16
|
+
|
17
|
+
|
18
|
+
DATE_MIN = Date.from_py_date(dt.date.min)
|
19
|
+
DATE_MAX = Date.from_py_date(dt.date.max)
|
20
|
+
PLAIN_DATE_TIME_MIN = PlainDateTime.from_py_datetime(dt.datetime.min) # noqa: DTZ901
|
21
|
+
PLAIN_DATE_TIME_MAX = PlainDateTime.from_py_datetime(dt.datetime.max) # noqa: DTZ901
|
22
|
+
ZONED_DATE_TIME_MIN = PLAIN_DATE_TIME_MIN.assume_tz(UTC.key)
|
23
|
+
ZONED_DATE_TIME_MAX = PLAIN_DATE_TIME_MAX.assume_tz(UTC.key)
|
24
|
+
DATE_TIME_DELTA_MIN = DateTimeDelta(days=-3652059, seconds=-316192377600)
|
25
|
+
DATE_TIME_DELTA_MAX = DateTimeDelta(days=3652059, seconds=316192377600)
|
26
|
+
DATE_DELTA_MIN = DATE_TIME_DELTA_MIN.date_part()
|
27
|
+
DATE_DELTA_MAX = DATE_TIME_DELTA_MAX.date_part()
|
28
|
+
TIME_DELTA_MIN = DATE_TIME_DELTA_MIN.time_part()
|
29
|
+
TIME_DELTA_MAX = DATE_TIME_DELTA_MAX.time_part()
|
30
|
+
|
31
|
+
|
32
|
+
##
|
33
|
+
|
34
|
+
|
35
|
+
def from_timestamp(i: float, /, *, time_zone: TimeZoneLike = UTC) -> ZonedDateTime:
|
36
|
+
"""Get a zoned datetime from a timestamp."""
|
37
|
+
return ZonedDateTime.from_timestamp(i, tz=get_time_zone_name(time_zone))
|
38
|
+
|
39
|
+
|
40
|
+
def from_timestamp_millis(i: int, /, *, time_zone: TimeZoneLike = UTC) -> ZonedDateTime:
|
41
|
+
"""Get a zoned datetime from a timestamp (in milliseconds)."""
|
42
|
+
return ZonedDateTime.from_timestamp_millis(i, tz=get_time_zone_name(time_zone))
|
43
|
+
|
44
|
+
|
45
|
+
def from_timestamp_nanos(i: int, /, *, time_zone: TimeZoneLike = UTC) -> ZonedDateTime:
|
46
|
+
"""Get a zoned datetime from a timestamp (in nanoseconds)."""
|
47
|
+
return ZonedDateTime.from_timestamp_nanos(i, tz=get_time_zone_name(time_zone))
|
48
|
+
|
49
|
+
|
50
|
+
##
|
51
|
+
|
52
|
+
|
53
|
+
def get_now(*, time_zone: TimeZoneLike = UTC) -> ZonedDateTime:
|
54
|
+
"""Get the current zoned datetime."""
|
55
|
+
return ZonedDateTime.now(get_time_zone_name(time_zone))
|
56
|
+
|
57
|
+
|
58
|
+
NOW_UTC = get_now(time_zone=UTC)
|
59
|
+
|
60
|
+
|
61
|
+
def get_now_local() -> ZonedDateTime:
|
62
|
+
"""Get the current local time."""
|
63
|
+
return get_now(time_zone="local")
|
64
|
+
|
65
|
+
|
66
|
+
##
|
67
|
+
|
68
|
+
|
69
|
+
class WheneverLogRecord(LogRecord):
|
70
|
+
"""Log record powered by `whenever`."""
|
71
|
+
|
72
|
+
zoned_datetime: str
|
73
|
+
|
74
|
+
@override
|
75
|
+
def __init__(
|
76
|
+
self,
|
77
|
+
name: str,
|
78
|
+
level: int,
|
79
|
+
pathname: str,
|
80
|
+
lineno: int,
|
81
|
+
msg: object,
|
82
|
+
args: Any,
|
83
|
+
exc_info: Any,
|
84
|
+
func: str | None = None,
|
85
|
+
sinfo: str | None = None,
|
86
|
+
) -> None:
|
87
|
+
super().__init__(
|
88
|
+
name, level, pathname, lineno, msg, args, exc_info, func, sinfo
|
89
|
+
)
|
90
|
+
length = self._get_length()
|
91
|
+
plain = format(get_now_local().to_plain().format_common_iso(), f"{length}s")
|
92
|
+
time_zone = self._get_time_zone_key()
|
93
|
+
self.zoned_datetime = f"{plain}[{time_zone}]"
|
94
|
+
|
95
|
+
@classmethod
|
96
|
+
@cache
|
97
|
+
def _get_time_zone(cls) -> ZoneInfo:
|
98
|
+
"""Get the local timezone."""
|
99
|
+
try:
|
100
|
+
from utilities.tzlocal import get_local_time_zone
|
101
|
+
except ModuleNotFoundError: # pragma: no cover
|
102
|
+
return UTC
|
103
|
+
return get_local_time_zone()
|
104
|
+
|
105
|
+
@classmethod
|
106
|
+
@cache
|
107
|
+
def _get_time_zone_key(cls) -> str:
|
108
|
+
"""Get the local timezone as a string."""
|
109
|
+
return cls._get_time_zone().key
|
110
|
+
|
111
|
+
@classmethod
|
112
|
+
@cache
|
113
|
+
def _get_length(cls) -> int:
|
114
|
+
"""Get maximum length of a formatted string."""
|
115
|
+
now = get_now_local().replace(nanosecond=1000).to_plain()
|
116
|
+
return len(now.format_common_iso())
|
117
|
+
|
118
|
+
|
119
|
+
__all__ = [
|
120
|
+
"DATE_DELTA_MAX",
|
121
|
+
"DATE_DELTA_MIN",
|
122
|
+
"DATE_MAX",
|
123
|
+
"DATE_MIN",
|
124
|
+
"DATE_TIME_DELTA_MAX",
|
125
|
+
"DATE_TIME_DELTA_MIN",
|
126
|
+
"PLAIN_DATE_TIME_MAX",
|
127
|
+
"PLAIN_DATE_TIME_MIN",
|
128
|
+
"TIME_DELTA_MAX",
|
129
|
+
"TIME_DELTA_MIN",
|
130
|
+
"ZONED_DATE_TIME_MAX",
|
131
|
+
"ZONED_DATE_TIME_MIN",
|
132
|
+
"WheneverLogRecord",
|
133
|
+
"from_timestamp",
|
134
|
+
"from_timestamp_millis",
|
135
|
+
"from_timestamp_nanos",
|
136
|
+
"get_now",
|
137
|
+
"get_now",
|
138
|
+
"get_now_local",
|
139
|
+
]
|
File without changes
|
File without changes
|