dycw-utilities 0.154.0__py3-none-any.whl → 0.155.1__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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.154.0
3
+ Version: 0.155.1
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -12,8 +12,7 @@ Provides-Extra: logging
12
12
  Requires-Dist: coloredlogs<15.1,>=15.0.1; extra == 'logging'
13
13
  Provides-Extra: test
14
14
  Requires-Dist: dycw-pytest-only<2.2,>=2.1.1; extra == 'test'
15
- Requires-Dist: hypothesis<6.137,>=6.136.7; extra == 'test'
16
- Requires-Dist: pudb<2025.2,>=2025.1; extra == 'test'
15
+ Requires-Dist: hypothesis<6.138,>=6.137.1; extra == 'test'
17
16
  Requires-Dist: pytest-asyncio<1.2,>=1.1.0; extra == 'test'
18
17
  Requires-Dist: pytest-cov<6.3,>=6.2.1; extra == 'test'
19
18
  Requires-Dist: pytest-instafail<0.6,>=0.5.0; extra == 'test'
@@ -1,4 +1,4 @@
1
- utilities/__init__.py,sha256=AggdQNmHiytWszaBPt8g3NE_WaSx4IoGrhZ8f-NLdAI,60
1
+ utilities/__init__.py,sha256=VUm6D-MbPENpEcdDNizwkQ7ZdWupTs1IZmUsMeC93k0,60
2
2
  utilities/altair.py,sha256=92E2lCdyHY4Zb-vCw6rEJIsWdKipuu-Tu2ab1ufUfAk,9079
3
3
  utilities/asyncio.py,sha256=QXkTtugXkqtYt7Do23zgYErqzdp6jwzPpV_SP9fJ1gI,16780
4
4
  utilities/atomicwrites.py,sha256=tPo6r-Rypd9u99u66B9z86YBPpnLrlHtwox_8Z7T34Y,5790
@@ -22,7 +22,7 @@ utilities/getpass.py,sha256=DfN5UgMAtFCqS3dSfFHUfqIMZX2shXvwphOz_6J6f6A,103
22
22
  utilities/gzip.py,sha256=fkGP3KdsBfXlstodT4wtlp-PwNyUsogpbDCVVVGdsm4,781
23
23
  utilities/hashlib.py,sha256=SVTgtguur0P4elppvzOBbLEjVM3Pea0eWB61yg2ilxo,309
24
24
  utilities/http.py,sha256=TsavEfHlRtlLaeV21Z6KZh0qbPw-kvD1zsQdZ7Kep5Q,977
25
- utilities/hypothesis.py,sha256=m44niSfuzuhgn7IQ1UOwUGgiu68xz4a6LHxB0IE6fNE,40341
25
+ utilities/hypothesis.py,sha256=2lAUvuXj_zswg-8Ot0ZuBzTbZyaRYWRmeR8qSF7Mmys,43817
26
26
  utilities/importlib.py,sha256=mV1xT_O_zt_GnZZ36tl3xOmMaN_3jErDWY54fX39F6Y,429
27
27
  utilities/inflect.py,sha256=v7YkOWSu8NAmVghPcf4F3YBZQoJCS47_DLf9jbfWIs0,581
28
28
  utilities/ipython.py,sha256=V2oMYHvEKvlNBzxDXdLvKi48oUq2SclRg5xasjaXStw,763
@@ -39,13 +39,13 @@ utilities/more_itertools.py,sha256=rklJ5vpvXr_H5pAGpWmwVpqtBVehoJ0-jBGYsZbux3M,1
39
39
  utilities/numpy.py,sha256=Xn23sA2ZbVNqwUYEgNJD3XBYH6IbCri_WkHSNhg3NkY,26122
40
40
  utilities/operator.py,sha256=nhxn5q6CFNzUm1wpTwWPCu9JGCqVHSlaJf0o1-efoII,3616
41
41
  utilities/optuna.py,sha256=C-fhWYiXHVPo1l8QctYkFJ4DyhbSrGorzP1dJb_qvd8,1933
42
- utilities/orjson.py,sha256=Gzxn-s55pGFKV8DdsYXpp-Gr3r-5KdacYUd_GHMBogM,40088
42
+ utilities/orjson.py,sha256=Ll0U172ITMqOJc3kjV90C0eI-EWzSXlMHSdUBaUSe80,41499
43
43
  utilities/os.py,sha256=mFvjydySvjtSXpk7tLStUJcndauAoujxUUmj_CO7LWY,3778
44
44
  utilities/parse.py,sha256=JcJn5yXKhIWXBCwgBdPsyu7Hvcuw6kyEdqvaebCaI9k,17951
45
45
  utilities/pathlib.py,sha256=qGuU8XPmdgGpy8tOMUgelfXx3kxI8h9IaV3TI_06QGE,8428
46
46
  utilities/pickle.py,sha256=MBT2xZCsv0pH868IXLGKnlcqNx2IRVKYNpRcqiQQqxw,653
47
47
  utilities/platform.py,sha256=pTn7gw6N4T6LdKrf0virwarof_mze9WtoQlrGMzhGVI,2798
48
- utilities/polars.py,sha256=I07Bk_Vp2T434qXkCKxSVQIkFJc1d8YkOH48fprypB0,78436
48
+ utilities/polars.py,sha256=yedkwwcyX35lreA2CeOGVRyOrXrs8DKk98T9dWpnxCo,79844
49
49
  utilities/polars_ols.py,sha256=Uc9V5kvlWZ5cU93lKZ-cfAKdVFFw81tqwLW9PxtUvMs,5618
50
50
  utilities/postgres.py,sha256=ynCTTaF-bVEOSW-KEAR-dlLh_hYjeVVjm__-4pEU8Zk,12269
51
51
  utilities/pottery.py,sha256=HJ96oLRarTP37Vhg0WTyB3yAu2hETeg6HgRmpDIqyUs,6581
@@ -54,7 +54,7 @@ utilities/psutil.py,sha256=KUlu4lrUw9Zg1V7ZGetpWpGb9DB8l_SSDWGbANFNCPU,2104
54
54
  utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
55
  utilities/pyinstrument.py,sha256=NZCZz2nBo0BLJ9DTf7H_Q_KGxvsf2S2M3h0qYoYh2kw,804
56
56
  utilities/pytest.py,sha256=2HHfAWkzZeK2OAzL2F49EDKooMkfDoGqg8Ev4cHC_N8,7869
57
- utilities/pytest_regressions.py,sha256=ocjHTtfOeiGfQAKIei8pKNd61sxN9dawrJJ9gPt2wzA,4097
57
+ utilities/pytest_regressions.py,sha256=8by5DWEL89Y469TI5AzX1pMy3NJWVtjEg2xQdOOdYuM,4169
58
58
  utilities/random.py,sha256=hZlH4gnAtoaofWswuJYjcygejrY8db4CzP-z_adO2Mo,4165
59
59
  utilities/re.py,sha256=S4h-DLL6ScMPqjboZ_uQ1BVTJajrqV06r_81D--_HCE,4573
60
60
  utilities/redis.py,sha256=2fdveFbqL2pEAeyiVuN_Je8nSM_IZHeahPduMHhFRzY,28381
@@ -72,23 +72,23 @@ utilities/tempfile.py,sha256=HxB2BF28CcecDJLQ3Bx2Ej-Pb6RJc6W9ngSpB9CnP4k,2018
72
72
  utilities/text.py,sha256=uwCDgpEunYruyh6sKMfNWK3Rp5H3ndpKRAkq86CBNys,13043
73
73
  utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
74
74
  utilities/timer.py,sha256=oXfTii6ymu57niP0BDGZjFD55LEHi2a19kqZKiTgaFQ,2588
75
- utilities/traceback.py,sha256=TjO7em98FDFLvROZ7gi2UJftFWNuSTkbCrf7mk-fg28,9416
75
+ utilities/traceback.py,sha256=1k5JgumSMaqAGLd0dZ36CtPS0EGaglxTr29r2Dz4D60,9457
76
76
  utilities/typed_settings.py,sha256=SFWqS3lAzV7IfNRwqFcTk0YynTcQ7BmrcW2mr_KUnos,4466
77
77
  utilities/types.py,sha256=L4cjFPyFZX58Urfw0S_i-XRywPIFyuSLOieewj0qqsM,18516
78
- utilities/typing.py,sha256=Z-_XDaWyT_6wIo3qfNK-hvRlzxP2Jxa9PgXzm5rDYRA,13790
78
+ utilities/typing.py,sha256=7ZgCNZwA6oaiwpSJIS9Rj3i3MbRBYHMqbC3jMe5KiNg,13992
79
79
  utilities/tzdata.py,sha256=fgNVj66yUbCSI_-vrRVzSD3gtf-L_8IEJEPjP_Jel5Y,266
80
80
  utilities/tzlocal.py,sha256=KyCXEgCTjqGFx-389JdTuhMRUaT06U1RCMdWoED-qro,728
81
81
  utilities/uuid.py,sha256=nQZs6tFX4mqtc2Ku3KqjloYCqwpTKeTj8eKwQwh3FQI,1572
82
82
  utilities/version.py,sha256=ipBj5-WYY_nelp2uwFlApfWWCzTLzPwpovUi9x_OBMs,5085
83
83
  utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
84
- utilities/whenever.py,sha256=gPnFKWws4_tjiHPLzX1AukSwDjfMIO9Iim0DDNQyAqY,57532
84
+ utilities/whenever.py,sha256=vsoVRd8-KXVn9Ik5PveIGgOCuIGnMNqSEoPCsR0sZ30,57755
85
85
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
86
86
  utilities/zoneinfo.py,sha256=FBMcUQ4662Aq8SsuCL1OAhDQiyANmVjtb-C30DRrWoE,1966
87
87
  utilities/pytest_plugins/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
88
88
  utilities/pytest_plugins/pytest_randomly.py,sha256=B1qYVlExGOxTywq2r1SMi5o7btHLk2PNdY_b1p98dkE,409
89
89
  utilities/pytest_plugins/pytest_regressions.py,sha256=9v8kAXDM2ycIXJBimoiF4EgrwbUvxTycFWJiGR_GHhM,1466
90
- dycw_utilities-0.154.0.dist-info/METADATA,sha256=34hYZf8Cdia0JBt2zwn4AaQY6m0S7qY5VfKXDDjw6OY,1696
91
- dycw_utilities-0.154.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
- dycw_utilities-0.154.0.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
93
- dycw_utilities-0.154.0.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
94
- dycw_utilities-0.154.0.dist-info/RECORD,,
90
+ dycw_utilities-0.155.1.dist-info/METADATA,sha256=t4j9mkVdOy56nqyYGTiiODz6Zq0dOJUFTTz_4CTcQTg,1643
91
+ dycw_utilities-0.155.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
+ dycw_utilities-0.155.1.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
93
+ dycw_utilities-0.155.1.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
94
+ dycw_utilities-0.155.1.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.154.0"
3
+ __version__ = "0.155.1"
utilities/hypothesis.py CHANGED
@@ -50,10 +50,12 @@ from whenever import (
50
50
  ZonedDateTime,
51
51
  )
52
52
 
53
- from utilities.functions import ensure_int, ensure_str
53
+ from utilities.functions import ensure_int, ensure_str, max_nullable, min_nullable
54
54
  from utilities.math import (
55
55
  MAX_FLOAT32,
56
56
  MAX_FLOAT64,
57
+ MAX_INT8,
58
+ MAX_INT16,
57
59
  MAX_INT32,
58
60
  MAX_INT64,
59
61
  MAX_UINT8,
@@ -62,6 +64,8 @@ from utilities.math import (
62
64
  MAX_UINT64,
63
65
  MIN_FLOAT32,
64
66
  MIN_FLOAT64,
67
+ MIN_INT8,
68
+ MIN_INT16,
65
69
  MIN_INT32,
66
70
  MIN_INT64,
67
71
  MIN_UINT8,
@@ -90,6 +94,9 @@ from utilities.whenever import (
90
94
  DAY,
91
95
  TIME_DELTA_MAX,
92
96
  TIME_DELTA_MIN,
97
+ DatePeriod,
98
+ TimePeriod,
99
+ ZonedDateTimePeriod,
93
100
  to_date_time_delta,
94
101
  to_days,
95
102
  to_nanoseconds,
@@ -201,6 +208,26 @@ def date_deltas(
201
208
  ##
202
209
 
203
210
 
211
+ @composite
212
+ def date_periods(
213
+ draw: DrawFn,
214
+ /,
215
+ *,
216
+ min_value: MaybeSearchStrategy[Date | None] = None,
217
+ max_value: MaybeSearchStrategy[Date | None] = None,
218
+ two_digit: MaybeSearchStrategy[bool] = False,
219
+ ) -> DatePeriod:
220
+ """Strategy for generating date periods."""
221
+ min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
222
+ two_digit_ = draw2(draw, two_digit)
223
+ strategy = dates(min_value=min_value_, max_value=max_value_, two_digit=two_digit_)
224
+ start, end = draw(pairs(strategy, sorted=True))
225
+ return DatePeriod(start, end)
226
+
227
+
228
+ ##
229
+
230
+
204
231
  @composite
205
232
  def date_time_deltas(
206
233
  draw: DrawFn,
@@ -209,6 +236,7 @@ def date_time_deltas(
209
236
  min_value: MaybeSearchStrategy[DateTimeDelta | None] = None,
210
237
  max_value: MaybeSearchStrategy[DateTimeDelta | None] = None,
211
238
  parsable: MaybeSearchStrategy[bool] = False,
239
+ nativable: MaybeSearchStrategy[bool] = False,
212
240
  ) -> DateTimeDelta:
213
241
  """Strategy for generating date deltas."""
214
242
  min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
@@ -230,7 +258,13 @@ def date_time_deltas(
230
258
  if draw2(draw, parsable):
231
259
  min_nanos = max(min_nanos, to_nanoseconds(DATE_TIME_DELTA_PARSABLE_MIN))
232
260
  max_nanos = min(max_nanos, to_nanoseconds(DATE_TIME_DELTA_PARSABLE_MAX))
233
- nanos = draw(integers(min_value=min_nanos, max_value=max_nanos))
261
+ if draw2(draw, nativable):
262
+ min_micros, _ = divmod(min_nanos, 1000)
263
+ max_micros, _ = divmod(max_nanos, 1000)
264
+ micros = draw(integers(min_value=min_micros + 1, max_value=max_micros))
265
+ nanos = 1000 * micros
266
+ else:
267
+ nanos = draw(integers(min_value=min_nanos, max_value=max_nanos))
234
268
  return to_date_time_delta(nanos)
235
269
 
236
270
 
@@ -373,13 +407,13 @@ def float32s(
373
407
  draw: DrawFn,
374
408
  /,
375
409
  *,
376
- min_value: MaybeSearchStrategy[float] = MIN_FLOAT32,
377
- max_value: MaybeSearchStrategy[float] = MAX_FLOAT32,
410
+ min_value: MaybeSearchStrategy[float | None] = None,
411
+ max_value: MaybeSearchStrategy[float | None] = None,
378
412
  ) -> float:
379
413
  """Strategy for generating float32s."""
380
414
  min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
381
- min_value_ = max(min_value_, MIN_FLOAT32)
382
- max_value_ = min(max_value_, MAX_FLOAT32)
415
+ min_value_ = max_nullable([min_value_, MIN_FLOAT32])
416
+ max_value_ = min_nullable([max_value_, MAX_FLOAT32])
383
417
  if is_zero(min_value_) and is_zero(max_value_):
384
418
  min_value_ = max_value_ = 0.0
385
419
  return draw(floats(min_value_, max_value_, width=32))
@@ -390,13 +424,13 @@ def float64s(
390
424
  draw: DrawFn,
391
425
  /,
392
426
  *,
393
- min_value: MaybeSearchStrategy[float] = MIN_FLOAT64,
394
- max_value: MaybeSearchStrategy[float] = MAX_FLOAT64,
427
+ min_value: MaybeSearchStrategy[float | None] = None,
428
+ max_value: MaybeSearchStrategy[float | None] = None,
395
429
  ) -> float:
396
430
  """Strategy for generating float64s."""
397
431
  min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
398
- min_value_ = max(min_value_, MIN_FLOAT64)
399
- max_value_ = min(max_value_, MAX_FLOAT64)
432
+ min_value_ = max_nullable([min_value_, MIN_FLOAT64])
433
+ max_value_ = min_nullable([max_value_, MAX_FLOAT64])
400
434
  if is_zero(min_value_) and is_zero(max_value_):
401
435
  min_value_ = max_value_ = 0.0
402
436
  return draw(floats(min_value_, max_value_, width=64))
@@ -601,18 +635,48 @@ def int_arrays(
601
635
  ##
602
636
 
603
637
 
638
+ @composite
639
+ def int8s(
640
+ draw: DrawFn,
641
+ /,
642
+ *,
643
+ min_value: MaybeSearchStrategy[int | None] = None,
644
+ max_value: MaybeSearchStrategy[int | None] = None,
645
+ ) -> int:
646
+ """Strategy for generating int8s."""
647
+ min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
648
+ min_value_ = max_nullable([min_value_, MIN_INT8])
649
+ max_value_ = min_nullable([max_value_, MAX_INT8])
650
+ return draw(integers(min_value=min_value_, max_value=max_value_))
651
+
652
+
653
+ @composite
654
+ def int16s(
655
+ draw: DrawFn,
656
+ /,
657
+ *,
658
+ min_value: MaybeSearchStrategy[int | None] = None,
659
+ max_value: MaybeSearchStrategy[int | None] = None,
660
+ ) -> int:
661
+ """Strategy for generating int16s."""
662
+ min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
663
+ min_value_ = max_nullable([min_value_, MIN_INT16])
664
+ max_value_ = min_nullable([max_value_, MAX_INT16])
665
+ return draw(integers(min_value=min_value_, max_value=max_value_))
666
+
667
+
604
668
  @composite
605
669
  def int32s(
606
670
  draw: DrawFn,
607
671
  /,
608
672
  *,
609
- min_value: MaybeSearchStrategy[int] = MIN_INT32,
610
- max_value: MaybeSearchStrategy[int] = MAX_INT32,
673
+ min_value: MaybeSearchStrategy[int | None] = None,
674
+ max_value: MaybeSearchStrategy[int | None] = None,
611
675
  ) -> int:
612
676
  """Strategy for generating int32s."""
613
677
  min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
614
- min_value_ = max(min_value_, MIN_INT32)
615
- max_value_ = min(max_value_, MAX_INT32)
678
+ min_value_ = max_nullable([min_value_, MIN_INT32])
679
+ max_value_ = min_nullable([max_value_, MAX_INT32])
616
680
  return draw(integers(min_value_, max_value_))
617
681
 
618
682
 
@@ -621,13 +685,13 @@ def int64s(
621
685
  draw: DrawFn,
622
686
  /,
623
687
  *,
624
- min_value: MaybeSearchStrategy[int] = MIN_INT64,
625
- max_value: MaybeSearchStrategy[int] = MAX_INT64,
688
+ min_value: MaybeSearchStrategy[int | None] = None,
689
+ max_value: MaybeSearchStrategy[int | None] = None,
626
690
  ) -> int:
627
691
  """Strategy for generating int64s."""
628
692
  min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
629
- min_value_ = max(min_value_, MIN_INT64)
630
- max_value_ = min(max_value_, MAX_INT64)
693
+ min_value_ = max_nullable([min_value_, MIN_INT64])
694
+ max_value_ = min_nullable([max_value_, MAX_INT64])
631
695
  return draw(integers(min_value_, max_value_))
632
696
 
633
697
 
@@ -780,7 +844,7 @@ def _path_parts(draw: DrawFn, /) -> str:
780
844
 
781
845
 
782
846
  @composite
783
- def plain_datetimes(
847
+ def plain_date_times(
784
848
  draw: DrawFn,
785
849
  /,
786
850
  *,
@@ -1174,6 +1238,24 @@ def time_deltas(
1174
1238
  ##
1175
1239
 
1176
1240
 
1241
+ @composite
1242
+ def time_periods(
1243
+ draw: DrawFn,
1244
+ /,
1245
+ *,
1246
+ min_value: MaybeSearchStrategy[Time | None] = None,
1247
+ max_value: MaybeSearchStrategy[Time | None] = None,
1248
+ ) -> TimePeriod:
1249
+ """Strategy for generating time periods."""
1250
+ min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
1251
+ strategy = times(min_value=min_value_, max_value=max_value_)
1252
+ start, end = draw(pairs(strategy, sorted=True))
1253
+ return TimePeriod(start, end)
1254
+
1255
+
1256
+ ##
1257
+
1258
+
1177
1259
  @composite
1178
1260
  def times(
1179
1261
  draw: DrawFn,
@@ -1235,13 +1317,13 @@ def uint8s(
1235
1317
  draw: DrawFn,
1236
1318
  /,
1237
1319
  *,
1238
- min_value: MaybeSearchStrategy[int] = MIN_UINT8,
1239
- max_value: MaybeSearchStrategy[int] = MAX_UINT8,
1320
+ min_value: MaybeSearchStrategy[int | None] = None,
1321
+ max_value: MaybeSearchStrategy[int | None] = None,
1240
1322
  ) -> int:
1241
1323
  """Strategy for generating uint8s."""
1242
1324
  min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
1243
- min_value_ = max(min_value_, MIN_UINT8)
1244
- max_value_ = min(max_value_, MAX_UINT8)
1325
+ min_value_ = max_nullable([min_value_, MIN_UINT8])
1326
+ max_value_ = min_nullable([max_value_, MAX_UINT8])
1245
1327
  return draw(integers(min_value=min_value_, max_value=max_value_))
1246
1328
 
1247
1329
 
@@ -1250,13 +1332,13 @@ def uint16s(
1250
1332
  draw: DrawFn,
1251
1333
  /,
1252
1334
  *,
1253
- min_value: MaybeSearchStrategy[int] = MIN_UINT16,
1254
- max_value: MaybeSearchStrategy[int] = MAX_UINT16,
1335
+ min_value: MaybeSearchStrategy[int | None] = None,
1336
+ max_value: MaybeSearchStrategy[int | None] = None,
1255
1337
  ) -> int:
1256
1338
  """Strategy for generating uint16s."""
1257
1339
  min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
1258
- min_value_ = max(min_value_, MIN_UINT16)
1259
- max_value_ = min(max_value_, MAX_UINT16)
1340
+ min_value_ = max_nullable([min_value_, MIN_UINT16])
1341
+ max_value_ = min_nullable([max_value_, MAX_UINT16])
1260
1342
  return draw(integers(min_value=min_value_, max_value=max_value_))
1261
1343
 
1262
1344
 
@@ -1265,13 +1347,13 @@ def uint32s(
1265
1347
  draw: DrawFn,
1266
1348
  /,
1267
1349
  *,
1268
- min_value: MaybeSearchStrategy[int] = MIN_UINT32,
1269
- max_value: MaybeSearchStrategy[int] = MAX_UINT32,
1350
+ min_value: MaybeSearchStrategy[int | None] = None,
1351
+ max_value: MaybeSearchStrategy[int | None] = None,
1270
1352
  ) -> int:
1271
1353
  """Strategy for generating uint32s."""
1272
1354
  min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
1273
- min_value_ = max(min_value_, MIN_UINT32)
1274
- max_value_ = min(max_value_, MAX_UINT32)
1355
+ min_value_ = max_nullable([min_value_, MIN_UINT32])
1356
+ max_value_ = min_nullable([max_value_, MAX_UINT32])
1275
1357
  return draw(integers(min_value=min_value_, max_value=max_value_))
1276
1358
 
1277
1359
 
@@ -1280,13 +1362,13 @@ def uint64s(
1280
1362
  draw: DrawFn,
1281
1363
  /,
1282
1364
  *,
1283
- min_value: MaybeSearchStrategy[int] = MIN_UINT64,
1284
- max_value: MaybeSearchStrategy[int] = MAX_UINT64,
1365
+ min_value: MaybeSearchStrategy[int | None] = None,
1366
+ max_value: MaybeSearchStrategy[int | None] = None,
1285
1367
  ) -> int:
1286
1368
  """Strategy for generating uint64s."""
1287
1369
  min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
1288
- min_value_ = max(min_value_, MIN_UINT64)
1289
- max_value_ = min(max_value_, MAX_UINT64)
1370
+ min_value_ = max_nullable([min_value_, MIN_UINT64])
1371
+ max_value_ = min_nullable([max_value_, MAX_UINT64])
1290
1372
  return draw(integers(min_value=min_value_, max_value=max_value_))
1291
1373
 
1292
1374
 
@@ -1374,7 +1456,29 @@ def year_months(
1374
1456
 
1375
1457
 
1376
1458
  @composite
1377
- def zoned_datetimes(
1459
+ def zoned_date_time_periods(
1460
+ draw: DrawFn,
1461
+ /,
1462
+ *,
1463
+ min_value: MaybeSearchStrategy[PlainDateTime | ZonedDateTime | None] = None,
1464
+ max_value: MaybeSearchStrategy[PlainDateTime | ZonedDateTime | None] = None,
1465
+ time_zone: MaybeSearchStrategy[TimeZoneLike] = UTC,
1466
+ ) -> ZonedDateTimePeriod:
1467
+ """Strategy for generating zoned date-time periods."""
1468
+ min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
1469
+ time_zone_: TimeZoneLike = draw2(draw, time_zone)
1470
+ strategy = zoned_date_times(
1471
+ min_value=min_value_, max_value=max_value_, time_zone=time_zone_
1472
+ )
1473
+ start, end = draw(pairs(strategy, sorted=True))
1474
+ return ZonedDateTimePeriod(start, end)
1475
+
1476
+
1477
+ ##
1478
+
1479
+
1480
+ @composite
1481
+ def zoned_date_times(
1378
1482
  draw: DrawFn,
1379
1483
  /,
1380
1484
  *,
@@ -1382,7 +1486,7 @@ def zoned_datetimes(
1382
1486
  max_value: MaybeSearchStrategy[PlainDateTime | ZonedDateTime | None] = None,
1383
1487
  time_zone: MaybeSearchStrategy[TimeZoneLike] = UTC,
1384
1488
  ) -> ZonedDateTime:
1385
- """Strategy for generating zoned datetimes."""
1489
+ """Strategy for generating zoned date-times."""
1386
1490
  min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
1387
1491
  time_zone_ = ensure_time_zone(draw2(draw, time_zone))
1388
1492
  match min_value_:
@@ -1401,7 +1505,7 @@ def zoned_datetimes(
1401
1505
  max_value_ = max_value_.to_tz(time_zone_.key).to_plain()
1402
1506
  case never:
1403
1507
  assert_never(never)
1404
- plain = draw(plain_datetimes(min_value=min_value_, max_value=max_value_))
1508
+ plain = draw(plain_date_times(min_value=min_value_, max_value=max_value_))
1405
1509
  with (
1406
1510
  assume_does_not_raise(RepeatedTime),
1407
1511
  assume_does_not_raise(SkippedTime),
@@ -1415,7 +1519,7 @@ def zoned_datetimes(
1415
1519
  return zoned
1416
1520
 
1417
1521
 
1418
- zoned_datetimes_2000 = zoned_datetimes(
1522
+ zoned_date_times_2000 = zoned_date_times(
1419
1523
  min_value=ZonedDateTime(2000, 1, 1, tz=UTC.key),
1420
1524
  max_value=ZonedDateTime(2000, 12, 31, tz=UTC.key),
1421
1525
  )
@@ -1427,6 +1531,7 @@ __all__ = [
1427
1531
  "assume_does_not_raise",
1428
1532
  "bool_arrays",
1429
1533
  "date_deltas",
1534
+ "date_periods",
1430
1535
  "date_time_deltas",
1431
1536
  "dates",
1432
1537
  "draw2",
@@ -1438,6 +1543,8 @@ __all__ = [
1438
1543
  "hashables",
1439
1544
  "import_froms",
1440
1545
  "imports",
1546
+ "int8s",
1547
+ "int16s",
1441
1548
  "int32s",
1442
1549
  "int64s",
1443
1550
  "int_arrays",
@@ -1446,7 +1553,7 @@ __all__ = [
1446
1553
  "numbers",
1447
1554
  "pairs",
1448
1555
  "paths",
1449
- "plain_datetimes",
1556
+ "plain_date_times",
1450
1557
  "py_datetimes",
1451
1558
  "random_states",
1452
1559
  "sentinels",
@@ -1463,6 +1570,7 @@ __all__ = [
1463
1570
  "text_digits",
1464
1571
  "text_printable",
1465
1572
  "time_deltas",
1573
+ "time_periods",
1466
1574
  "times",
1467
1575
  "triples",
1468
1576
  "uint8s",
@@ -1472,6 +1580,7 @@ __all__ = [
1472
1580
  "urls",
1473
1581
  "versions",
1474
1582
  "year_months",
1475
- "zoned_datetimes",
1476
- "zoned_datetimes_2000",
1583
+ "zoned_date_time_periods",
1584
+ "zoned_date_times",
1585
+ "zoned_date_times_2000",
1477
1586
  ]
utilities/orjson.py CHANGED
@@ -5,7 +5,7 @@ import re
5
5
  from collections.abc import Callable, Iterable, Mapping, Sequence
6
6
  from contextlib import suppress
7
7
  from dataclasses import dataclass, field, replace
8
- from enum import Enum, unique
8
+ from enum import Enum, StrEnum, unique
9
9
  from functools import cached_property, partial
10
10
  from itertools import chain
11
11
  from logging import Formatter, LogRecord
@@ -52,7 +52,12 @@ from utilities.math import MAX_INT64, MIN_INT64
52
52
  from utilities.types import Dataclass, LogLevel, MaybeIterable, PathLike, StrMapping
53
53
  from utilities.tzlocal import LOCAL_TIME_ZONE
54
54
  from utilities.version import Version, parse_version
55
- from utilities.whenever import from_timestamp
55
+ from utilities.whenever import (
56
+ DatePeriod,
57
+ TimePeriod,
58
+ ZonedDateTimePeriod,
59
+ from_timestamp,
60
+ )
56
61
 
57
62
  if TYPE_CHECKING:
58
63
  from collections.abc import Set as AbstractSet
@@ -65,10 +70,11 @@ if TYPE_CHECKING:
65
70
 
66
71
 
67
72
  @unique
68
- class _Prefixes(Enum):
73
+ class _Prefixes(StrEnum):
69
74
  dataclass = "dc"
70
75
  date = "d"
71
76
  date_delta = "dd"
77
+ date_period = "dp"
72
78
  date_time_delta = "D"
73
79
  enum = "e"
74
80
  exception_class = "Ex"
@@ -77,7 +83,7 @@ class _Prefixes(Enum):
77
83
  frozenset_ = "fr"
78
84
  list_ = "l"
79
85
  month_day = "md"
80
- none = "none"
86
+ none = "0"
81
87
  path = "p"
82
88
  plain_date_time = "pd"
83
89
  py_date = "!d"
@@ -87,12 +93,14 @@ class _Prefixes(Enum):
87
93
  set_ = "s"
88
94
  time = "ti"
89
95
  time_delta = "td"
96
+ time_period = "tp"
90
97
  tuple_ = "tu"
91
98
  unserializable = "un"
92
99
  uuid = "uu"
93
100
  version = "v"
94
101
  year_month = "ym"
95
102
  zoned_date_time = "zd"
103
+ zoned_date_time_period = "zp"
96
104
 
97
105
 
98
106
  type _DataclassHook = Callable[[type[Dataclass], StrMapping], StrMapping]
@@ -167,8 +175,10 @@ def _pre_process(
167
175
  return f"[{_Prefixes.date.value}]{date}"
168
176
  case DateDelta() as date:
169
177
  return f"[{_Prefixes.date_delta.value}]{date}"
170
- case DateTimeDelta() as date:
171
- return f"[{_Prefixes.date_time_delta.value}]{date}"
178
+ case DatePeriod() as period:
179
+ return f"[{_Prefixes.date_period.value}]{period.start},{period.end}"
180
+ case DateTimeDelta() as date_time_delta:
181
+ return f"[{_Prefixes.date_time_delta.value}]{date_time_delta}"
172
182
  case Exception() as error_:
173
183
  return {
174
184
  f"[{_Prefixes.exception_instance.value}|{type(error_).__qualname__}]": pre(
@@ -187,14 +197,16 @@ def _pre_process(
187
197
  return f"[{_Prefixes.month_day.value}]{month_day!s}"
188
198
  case Path() as path:
189
199
  return f"[{_Prefixes.path.value}]{path!s}"
190
- case PlainDateTime() as datetime:
191
- return f"[{_Prefixes.plain_date_time.value}]{datetime}"
192
- case str() as str_:
193
- return str_
200
+ case PlainDateTime() as date_time:
201
+ return f"[{_Prefixes.plain_date_time.value}]{date_time}"
202
+ case str() as text:
203
+ return text
194
204
  case Time() as time:
195
205
  return f"[{_Prefixes.time.value}]{time}"
196
206
  case TimeDelta() as time_delta:
197
207
  return f"[{_Prefixes.time_delta.value}]{time_delta}"
208
+ case TimePeriod() as period:
209
+ return f"[{_Prefixes.time_period.value}]{period.start},{period.end}"
198
210
  case type() as error_cls if issubclass(error_cls, Exception):
199
211
  return f"[{_Prefixes.exception_class.value}|{error_cls.__qualname__}]"
200
212
  case UUID() as uuid:
@@ -203,8 +215,12 @@ def _pre_process(
203
215
  return f"[{_Prefixes.version.value}]{version}"
204
216
  case YearMonth() as year_month:
205
217
  return f"[{_Prefixes.year_month.value}]{year_month}"
206
- case ZonedDateTime() as datetime:
207
- return f"[{_Prefixes.zoned_date_time.value}]{datetime}"
218
+ case ZonedDateTime() as date_time:
219
+ return f"[{_Prefixes.zoned_date_time.value}]{date_time}"
220
+ case ZonedDateTimePeriod() as period:
221
+ return (
222
+ f"[{_Prefixes.zoned_date_time_period.value}]{period.start},{period.end}"
223
+ )
208
224
  case dt.datetime() as py_datetime:
209
225
  match py_datetime.tzinfo:
210
226
  case None:
@@ -369,6 +385,7 @@ def deserialize(
369
385
  (
370
386
  _DATE_PATTERN,
371
387
  _DATE_DELTA_PATTERN,
388
+ _DATE_PERIOD_PATTERN,
372
389
  _DATE_TIME_DELTA_PATTERN,
373
390
  _FLOAT_PATTERN,
374
391
  _MONTH_DAY_PATTERN,
@@ -381,15 +398,18 @@ def deserialize(
381
398
  _PY_ZONED_DATE_TIME_PATTERN,
382
399
  _TIME_PATTERN,
383
400
  _TIME_DELTA_PATTERN,
401
+ _TIME_PERIOD_PATTERN,
384
402
  _UUID_PATTERN,
385
403
  _VERSION_PATTERN,
386
404
  _YEAR_MONTH_PATTERN,
387
405
  _ZONED_DATE_TIME_PATTERN,
406
+ _ZONED_DATE_TIME_PERIOD_PATTERN,
388
407
  ) = [
389
408
  re.compile(r"^\[" + p.value + r"\](" + ".*" + ")$")
390
409
  for p in [
391
410
  _Prefixes.date,
392
411
  _Prefixes.date_delta,
412
+ _Prefixes.date_period,
393
413
  _Prefixes.date_time_delta,
394
414
  _Prefixes.float_,
395
415
  _Prefixes.month_day,
@@ -402,10 +422,12 @@ def deserialize(
402
422
  _Prefixes.py_zoned_date_time,
403
423
  _Prefixes.time,
404
424
  _Prefixes.time_delta,
425
+ _Prefixes.time_period,
405
426
  _Prefixes.uuid,
406
427
  _Prefixes.version,
407
428
  _Prefixes.year_month,
408
429
  _Prefixes.zoned_date_time,
430
+ _Prefixes.zoned_date_time_period,
409
431
  ]
410
432
  ]
411
433
 
@@ -453,6 +475,9 @@ def _object_hook(
453
475
  return Date.parse_common_iso(match.group(1))
454
476
  if match := _DATE_DELTA_PATTERN.search(text):
455
477
  return DateDelta.parse_common_iso(match.group(1))
478
+ if match := _DATE_PERIOD_PATTERN.search(text):
479
+ start, end = map(Date.parse_common_iso, match.group(1).split(","))
480
+ return DatePeriod(start, end)
456
481
  if match := _DATE_TIME_DELTA_PATTERN.search(text):
457
482
  return DateTimeDelta.parse_common_iso(match.group(1))
458
483
  if match := _FLOAT_PATTERN.search(text):
@@ -475,6 +500,9 @@ def _object_hook(
475
500
  return Time.parse_common_iso(match.group(1))
476
501
  if match := _TIME_DELTA_PATTERN.search(text):
477
502
  return TimeDelta.parse_common_iso(match.group(1))
503
+ if match := _TIME_PERIOD_PATTERN.search(text):
504
+ start, end = map(Time.parse_common_iso, match.group(1).split(","))
505
+ return TimePeriod(start, end)
478
506
  if match := _UUID_PATTERN.search(text):
479
507
  return UUID(match.group(1))
480
508
  if match := _VERSION_PATTERN.search(text):
@@ -483,6 +511,11 @@ def _object_hook(
483
511
  return YearMonth.parse_common_iso(match.group(1))
484
512
  if match := _ZONED_DATE_TIME_PATTERN.search(text):
485
513
  return ZonedDateTime.parse_common_iso(match.group(1))
514
+ if match := _ZONED_DATE_TIME_PERIOD_PATTERN.search(text):
515
+ start, end = map(
516
+ ZonedDateTime.parse_common_iso, match.group(1).split(",")
517
+ )
518
+ return ZonedDateTimePeriod(start, end)
486
519
  if (
487
520
  exc_class := _object_hook_exception_class(
488
521
  text, data=data, objects=objects, redirects=redirects
utilities/polars.py CHANGED
@@ -15,11 +15,12 @@ from uuid import UUID
15
15
  from zoneinfo import ZoneInfo
16
16
 
17
17
  import polars as pl
18
+ import whenever
18
19
  from polars import (
19
20
  Boolean,
20
21
  DataFrame,
21
- Date,
22
22
  Datetime,
23
+ Duration,
23
24
  Expr,
24
25
  Float64,
25
26
  Int64,
@@ -43,16 +44,16 @@ from polars import (
43
44
  from polars._typing import PolarsDataType
44
45
  from polars.datatypes import DataType, DataTypeClass
45
46
  from polars.exceptions import (
46
- ColumnNotFoundError,
47
+ ColumnNotFoundError, # pyright: ignore[reportAttributeAccessIssue]
47
48
  NoRowsReturnedError,
48
- OutOfBoundsError,
49
+ OutOfBoundsError, # pyright: ignore[reportAttributeAccessIssue]
49
50
  PolarsInefficientMapWarning,
50
51
  )
51
52
  from polars.schema import Schema
52
53
  from polars.testing import assert_frame_equal, assert_series_equal
53
- from whenever import ZonedDateTime
54
+ from whenever import DateDelta, DateTimeDelta, PlainDateTime, TimeDelta, ZonedDateTime
54
55
 
55
- from utilities.dataclasses import _YieldFieldsInstance, yield_fields
56
+ from utilities.dataclasses import yield_fields
56
57
  from utilities.errors import ImpossibleCaseError
57
58
  from utilities.functions import (
58
59
  EnsureIntError,
@@ -93,14 +94,18 @@ from utilities.typing import (
93
94
  get_args,
94
95
  get_type_hints,
95
96
  is_frozenset_type,
96
- is_instance_gen,
97
97
  is_list_type,
98
98
  is_literal_type,
99
99
  is_optional_type,
100
100
  is_set_type,
101
- is_union_type,
102
101
  )
103
102
  from utilities.warnings import suppress_warnings
103
+ from utilities.whenever import (
104
+ DatePeriod,
105
+ TimePeriod,
106
+ ZonedDateTimePeriod,
107
+ to_py_time_delta,
108
+ )
104
109
  from utilities.zoneinfo import UTC, ensure_time_zone, get_time_zone_name
105
110
 
106
111
  if TYPE_CHECKING:
@@ -1052,14 +1057,35 @@ def dataclass_to_dataframe(
1052
1057
 
1053
1058
  def _dataclass_to_dataframe_cast(series: Series, /) -> Series:
1054
1059
  if series.dtype == Object:
1060
+ if series.map_elements(
1061
+ make_isinstance(whenever.Date), return_dtype=Boolean
1062
+ ).all():
1063
+ return series.map_elements(lambda x: x.py_date(), return_dtype=pl.Date)
1064
+ if series.map_elements(make_isinstance(DateDelta), return_dtype=Boolean).all():
1065
+ return series.map_elements(to_py_time_delta, return_dtype=Duration)
1066
+ if series.map_elements(
1067
+ make_isinstance(DateTimeDelta), return_dtype=Boolean
1068
+ ).all():
1069
+ return series.map_elements(to_py_time_delta, return_dtype=Duration)
1055
1070
  is_path = series.map_elements(make_isinstance(Path), return_dtype=Boolean).all()
1056
1071
  is_uuid = series.map_elements(make_isinstance(UUID), return_dtype=Boolean).all()
1057
1072
  if is_path or is_uuid:
1058
1073
  with suppress_warnings(category=PolarsInefficientMapWarning):
1059
1074
  return series.map_elements(str, return_dtype=String)
1060
- else: # pragma: no cover
1061
- msg = f"{is_path=}, f{is_uuid=}"
1062
- raise NotImplementedError(msg)
1075
+ if series.map_elements(
1076
+ make_isinstance(whenever.Time), return_dtype=Boolean
1077
+ ).all():
1078
+ return series.map_elements(lambda x: x.py_time(), return_dtype=pl.Time)
1079
+ if series.map_elements(make_isinstance(TimeDelta), return_dtype=Boolean).all():
1080
+ return series.map_elements(to_py_time_delta, return_dtype=Duration)
1081
+ if series.map_elements(
1082
+ make_isinstance(ZonedDateTime), return_dtype=Boolean
1083
+ ).all():
1084
+ return_dtype = zoned_datetime_dtype(time_zone=one({dt.tz for dt in series}))
1085
+ return series.map_elements(
1086
+ lambda x: x.py_datetime(), return_dtype=return_dtype
1087
+ )
1088
+ raise NotImplementedError(series) # pragma: no cover
1063
1089
  return series
1064
1090
 
1065
1091
 
@@ -1101,20 +1127,14 @@ def dataclass_to_schema(
1101
1127
  for field in yield_fields(
1102
1128
  obj, globalns=globalns, localns=localns, warn_name_errors=warn_name_errors
1103
1129
  ):
1104
- if is_dataclass_instance(field.value):
1130
+ if is_dataclass_instance(field.value) and not (
1131
+ isinstance(field.type_, type)
1132
+ and issubclass(field.type_, (DatePeriod, TimePeriod, ZonedDateTimePeriod))
1133
+ ):
1105
1134
  dtypes = dataclass_to_schema(
1106
1135
  field.value, globalns=globalns, localns=localns
1107
1136
  )
1108
1137
  dtype = struct_dtype(**dtypes)
1109
- elif field.type_ is dt.datetime:
1110
- dtype = _dataclass_to_schema_datetime(field)
1111
- elif is_union_type(field.type_) and set(
1112
- get_args(field.type_, optional_drop_none=True)
1113
- ) == {dt.date, dt.datetime}:
1114
- if is_instance_gen(field.value, dt.date):
1115
- dtype = Date
1116
- else:
1117
- dtype = _dataclass_to_schema_datetime(field)
1118
1138
  else:
1119
1139
  dtype = _dataclass_to_schema_one(
1120
1140
  field.type_, globalns=globalns, localns=localns
@@ -1123,14 +1143,6 @@ def dataclass_to_schema(
1123
1143
  return out
1124
1144
 
1125
1145
 
1126
- def _dataclass_to_schema_datetime(
1127
- field: _YieldFieldsInstance[dt.datetime], /
1128
- ) -> PolarsDataType:
1129
- if field.value.tzinfo is None:
1130
- return Datetime
1131
- return zoned_datetime_dtype(time_zone=ensure_time_zone(field.value.tzinfo))
1132
-
1133
-
1134
1146
  def _dataclass_to_schema_one(
1135
1147
  obj: Any,
1136
1148
  /,
@@ -1138,20 +1150,35 @@ def _dataclass_to_schema_one(
1138
1150
  globalns: StrMapping | None = None,
1139
1151
  localns: StrMapping | None = None,
1140
1152
  ) -> PolarsDataType:
1141
- if obj is bool:
1142
- return Boolean
1143
- if obj is int:
1144
- return Int64
1145
- if obj is float:
1146
- return Float64
1147
- if obj is str:
1148
- return String
1149
- if obj is dt.date:
1150
- return Date
1151
- if obj in {Path, UUID}:
1152
- return Object
1153
- if isinstance(obj, type) and issubclass(obj, enum.Enum):
1154
- return pl.Enum([e.name for e in obj])
1153
+ if isinstance(obj, type):
1154
+ if issubclass(obj, bool):
1155
+ return Boolean
1156
+ if issubclass(obj, int):
1157
+ return Int64
1158
+ if issubclass(obj, float):
1159
+ return Float64
1160
+ if issubclass(obj, str):
1161
+ return String
1162
+ if issubclass(
1163
+ obj,
1164
+ (
1165
+ DateDelta,
1166
+ DatePeriod,
1167
+ DateTimeDelta,
1168
+ Path,
1169
+ PlainDateTime,
1170
+ TimeDelta,
1171
+ TimePeriod,
1172
+ UUID,
1173
+ ZonedDateTime,
1174
+ ZonedDateTimePeriod,
1175
+ whenever.Date,
1176
+ whenever.Time,
1177
+ ),
1178
+ ):
1179
+ return Object
1180
+ if issubclass(obj, enum.Enum):
1181
+ return pl.Enum([e.name for e in obj])
1155
1182
  if is_dataclass_class(obj):
1156
1183
  out: dict[str, Any] = {}
1157
1184
  for field in yield_fields(obj, globalns=globalns, localns=localns):
@@ -2444,7 +2471,13 @@ def struct_from_dataclass(
2444
2471
  def _struct_from_dataclass_one(
2445
2472
  ann: Any, /, *, time_zone: TimeZoneLike | None = None
2446
2473
  ) -> PolarsDataType:
2447
- mapping = {bool: Boolean, dt.date: Date, float: Float64, int: Int64, str: String}
2474
+ mapping = {
2475
+ bool: Boolean,
2476
+ whenever.Date: pl.Date,
2477
+ float: Float64,
2478
+ int: Int64,
2479
+ str: String,
2480
+ }
2448
2481
  with suppress(KeyError):
2449
2482
  return mapping[ann]
2450
2483
  if ann is dt.datetime:
@@ -91,7 +91,9 @@ class PolarsRegressionFixture:
91
91
  def check(self, obj: Series | DataFrame, /, *, suffix: str | None = None) -> None:
92
92
  """Check the Series/DataFrame summary against the baseline."""
93
93
  from polars import DataFrame, Series, col
94
- from polars.exceptions import InvalidOperationError
94
+ from polars.exceptions import (
95
+ InvalidOperationError, # pyright: ignore[reportAttributeAccessIssue]
96
+ )
95
97
 
96
98
  data: StrMapping = {
97
99
  "describe": obj.describe(percentiles=[i / 10 for i in range(1, 10)]).rows(
utilities/traceback.py CHANGED
@@ -284,7 +284,7 @@ def _make_except_hook_inner(
284
284
  except SendToSlackError as error:
285
285
  _ = stderr.write(f"{error}\n")
286
286
  if to_bool(pudb): # pragma: no cover
287
- from pudb import post_mortem
287
+ from pudb import post_mortem # pyright: ignore[reportMissingImports]
288
288
 
289
289
  post_mortem(tb=traceback, e_type=exc_type, e_value=exc_val)
290
290
 
utilities/typing.py CHANGED
@@ -23,6 +23,7 @@ from typing import get_type_hints as _get_type_hints
23
23
  from uuid import UUID
24
24
  from warnings import warn
25
25
 
26
+ import whenever
26
27
  from whenever import (
27
28
  Date,
28
29
  DateDelta,
@@ -122,7 +123,6 @@ def get_type_hints(
122
123
  warn_name_errors: bool = False,
123
124
  ) -> dict[str, Any]:
124
125
  """Get the type hints of an object."""
125
- result: dict[str, Any] = obj.__annotations__
126
126
  _ = {
127
127
  Date,
128
128
  DateDelta,
@@ -136,10 +136,17 @@ def get_type_hints(
136
136
  TimeDelta,
137
137
  UUID,
138
138
  ZonedDateTime,
139
- dt,
139
+ whenever.Date,
140
+ whenever.DateDelta,
141
+ whenever.DateTimeDelta,
142
+ whenever.PlainDateTime,
143
+ whenever.Time,
144
+ whenever.TimeDelta,
145
+ whenever.ZonedDateTime,
140
146
  }
141
147
  globalns_use = globals() | ({} if globalns is None else dict(globalns))
142
148
  localns_use = {} if localns is None else dict(localns)
149
+ result: dict[str, Any] = obj.__annotations__
143
150
  try:
144
151
  hints = _get_type_hints(obj, globalns=globalns_use, localns=localns_use)
145
152
  except NameError as error:
utilities/whenever.py CHANGED
@@ -367,6 +367,14 @@ def get_now_local() -> ZonedDateTime:
367
367
  NOW_LOCAL = get_now_local()
368
368
 
369
369
 
370
+ def get_now_plain(time_zone: TimeZoneLike = UTC, /) -> PlainDateTime:
371
+ """Get the current zoned datetime."""
372
+ return get_now(time_zone).to_plain()
373
+
374
+
375
+ NOW_PLAIN = get_now_plain()
376
+
377
+
370
378
  ##
371
379
 
372
380
 
@@ -1927,6 +1935,7 @@ __all__ = [
1927
1935
  "MINUTE",
1928
1936
  "MONTH",
1929
1937
  "NOW_LOCAL",
1938
+ "NOW_PLAIN",
1930
1939
  "SECOND",
1931
1940
  "TIME_DELTA_MAX",
1932
1941
  "TIME_DELTA_MIN",
@@ -1966,6 +1975,7 @@ __all__ = [
1966
1975
  "from_timestamp_nanos",
1967
1976
  "get_now",
1968
1977
  "get_now_local",
1978
+ "get_now_plain",
1969
1979
  "get_today",
1970
1980
  "get_today_local",
1971
1981
  "mean_datetime",