dycw-utilities 0.129.10__py3-none-any.whl → 0.175.17__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.
Files changed (103) hide show
  1. dycw_utilities-0.175.17.dist-info/METADATA +34 -0
  2. dycw_utilities-0.175.17.dist-info/RECORD +103 -0
  3. dycw_utilities-0.175.17.dist-info/WHEEL +4 -0
  4. dycw_utilities-0.175.17.dist-info/entry_points.txt +4 -0
  5. utilities/__init__.py +1 -1
  6. utilities/altair.py +14 -14
  7. utilities/asyncio.py +350 -819
  8. utilities/atomicwrites.py +18 -6
  9. utilities/atools.py +77 -22
  10. utilities/cachetools.py +24 -29
  11. utilities/click.py +393 -237
  12. utilities/concurrent.py +8 -11
  13. utilities/contextlib.py +216 -17
  14. utilities/contextvars.py +20 -1
  15. utilities/cryptography.py +3 -3
  16. utilities/dataclasses.py +83 -118
  17. utilities/docker.py +293 -0
  18. utilities/enum.py +26 -23
  19. utilities/errors.py +17 -3
  20. utilities/fastapi.py +29 -65
  21. utilities/fpdf2.py +3 -3
  22. utilities/functions.py +169 -416
  23. utilities/functools.py +18 -19
  24. utilities/git.py +9 -30
  25. utilities/grp.py +28 -0
  26. utilities/gzip.py +31 -0
  27. utilities/http.py +3 -2
  28. utilities/hypothesis.py +738 -589
  29. utilities/importlib.py +17 -1
  30. utilities/inflect.py +25 -0
  31. utilities/iterables.py +194 -262
  32. utilities/jinja2.py +148 -0
  33. utilities/json.py +70 -0
  34. utilities/libcst.py +38 -17
  35. utilities/lightweight_charts.py +5 -9
  36. utilities/logging.py +345 -543
  37. utilities/math.py +18 -13
  38. utilities/memory_profiler.py +11 -15
  39. utilities/more_itertools.py +200 -131
  40. utilities/operator.py +33 -29
  41. utilities/optuna.py +6 -6
  42. utilities/orjson.py +272 -137
  43. utilities/os.py +61 -4
  44. utilities/parse.py +59 -61
  45. utilities/pathlib.py +281 -40
  46. utilities/permissions.py +298 -0
  47. utilities/pickle.py +2 -2
  48. utilities/platform.py +24 -5
  49. utilities/polars.py +1214 -430
  50. utilities/polars_ols.py +1 -1
  51. utilities/postgres.py +408 -0
  52. utilities/pottery.py +113 -26
  53. utilities/pqdm.py +10 -11
  54. utilities/psutil.py +6 -57
  55. utilities/pwd.py +28 -0
  56. utilities/pydantic.py +4 -54
  57. utilities/pydantic_settings.py +240 -0
  58. utilities/pydantic_settings_sops.py +76 -0
  59. utilities/pyinstrument.py +8 -10
  60. utilities/pytest.py +227 -121
  61. utilities/pytest_plugins/__init__.py +1 -0
  62. utilities/pytest_plugins/pytest_randomly.py +23 -0
  63. utilities/pytest_plugins/pytest_regressions.py +56 -0
  64. utilities/pytest_regressions.py +26 -46
  65. utilities/random.py +13 -9
  66. utilities/re.py +58 -28
  67. utilities/redis.py +401 -550
  68. utilities/scipy.py +1 -1
  69. utilities/sentinel.py +10 -0
  70. utilities/shelve.py +4 -1
  71. utilities/shutil.py +25 -0
  72. utilities/slack_sdk.py +36 -106
  73. utilities/sqlalchemy.py +502 -473
  74. utilities/sqlalchemy_polars.py +38 -94
  75. utilities/string.py +2 -3
  76. utilities/subprocess.py +1572 -0
  77. utilities/tempfile.py +86 -4
  78. utilities/testbook.py +50 -0
  79. utilities/text.py +165 -42
  80. utilities/timer.py +37 -65
  81. utilities/traceback.py +158 -929
  82. utilities/types.py +146 -116
  83. utilities/typing.py +531 -71
  84. utilities/tzdata.py +1 -53
  85. utilities/tzlocal.py +6 -23
  86. utilities/uuid.py +43 -5
  87. utilities/version.py +27 -26
  88. utilities/whenever.py +1776 -386
  89. utilities/zoneinfo.py +84 -22
  90. dycw_utilities-0.129.10.dist-info/METADATA +0 -241
  91. dycw_utilities-0.129.10.dist-info/RECORD +0 -96
  92. dycw_utilities-0.129.10.dist-info/WHEEL +0 -4
  93. dycw_utilities-0.129.10.dist-info/licenses/LICENSE +0 -21
  94. utilities/datetime.py +0 -1409
  95. utilities/eventkit.py +0 -402
  96. utilities/loguru.py +0 -144
  97. utilities/luigi.py +0 -228
  98. utilities/period.py +0 -324
  99. utilities/pyrsistent.py +0 -89
  100. utilities/python_dotenv.py +0 -105
  101. utilities/streamlit.py +0 -105
  102. utilities/sys.py +0 -87
  103. utilities/tenacity.py +0 -145
utilities/hypothesis.py CHANGED
@@ -4,26 +4,16 @@ import builtins
4
4
  import datetime as dt
5
5
  from contextlib import contextmanager
6
6
  from dataclasses import dataclass
7
- from datetime import timezone
8
7
  from enum import Enum, auto
9
- from functools import partial
10
8
  from math import ceil, floor, inf, isclose, isfinite, nan
11
9
  from os import environ
12
10
  from pathlib import Path
13
11
  from re import search
14
12
  from string import ascii_letters, ascii_lowercase, ascii_uppercase, digits, printable
15
13
  from subprocess import check_call
16
- from typing import (
17
- TYPE_CHECKING,
18
- Any,
19
- Literal,
20
- TypeVar,
21
- assert_never,
22
- cast,
23
- overload,
24
- override,
25
- )
14
+ from typing import TYPE_CHECKING, Any, Literal, assert_never, cast, overload, override
26
15
 
16
+ import hypothesis.strategies
27
17
  from hypothesis import HealthCheck, Phase, Verbosity, assume, settings
28
18
  from hypothesis.errors import InvalidArgument
29
19
  from hypothesis.strategies import (
@@ -33,7 +23,6 @@ from hypothesis.strategies import (
33
23
  booleans,
34
24
  characters,
35
25
  composite,
36
- dates,
37
26
  datetimes,
38
27
  floats,
39
28
  integers,
@@ -43,67 +32,93 @@ from hypothesis.strategies import (
43
32
  sampled_from,
44
33
  sets,
45
34
  text,
46
- timedeltas,
35
+ timezones,
47
36
  uuids,
48
37
  )
49
38
  from hypothesis.utils.conventions import not_set
50
-
51
- from utilities.datetime import (
52
- DATETIME_MAX_NAIVE,
53
- DATETIME_MAX_UTC,
54
- DATETIME_MIN_NAIVE,
55
- DATETIME_MIN_UTC,
56
- DAY,
57
- MAX_DATE_TWO_DIGIT_YEAR,
58
- MAX_MONTH,
59
- MIN_DATE_TWO_DIGIT_YEAR,
60
- MIN_MONTH,
61
- Month,
62
- date_duration_to_int,
63
- date_duration_to_timedelta,
64
- date_to_month,
65
- datetime_duration_to_float,
66
- datetime_duration_to_timedelta,
67
- round_datetime,
39
+ from whenever import (
40
+ Date,
41
+ DateDelta,
42
+ DateTimeDelta,
43
+ MonthDay,
44
+ PlainDateTime,
45
+ RepeatedTime,
46
+ SkippedTime,
47
+ Time,
48
+ TimeDelta,
49
+ TimeZoneNotFoundError,
50
+ YearMonth,
51
+ ZonedDateTime,
68
52
  )
53
+
69
54
  from utilities.functions import ensure_int, ensure_str, max_nullable, min_nullable
70
55
  from utilities.math import (
71
56
  MAX_FLOAT32,
72
57
  MAX_FLOAT64,
58
+ MAX_INT8,
59
+ MAX_INT16,
73
60
  MAX_INT32,
74
61
  MAX_INT64,
62
+ MAX_UINT8,
63
+ MAX_UINT16,
75
64
  MAX_UINT32,
76
65
  MAX_UINT64,
77
66
  MIN_FLOAT32,
78
67
  MIN_FLOAT64,
68
+ MIN_INT8,
69
+ MIN_INT16,
79
70
  MIN_INT32,
80
71
  MIN_INT64,
72
+ MIN_UINT8,
73
+ MIN_UINT16,
81
74
  MIN_UINT32,
82
75
  MIN_UINT64,
83
76
  is_zero,
84
77
  )
85
78
  from utilities.os import get_env_var
86
- from utilities.pathlib import temp_cwd
87
- from utilities.platform import IS_WINDOWS
88
- from utilities.sentinel import Sentinel, sentinel
79
+ from utilities.pathlib import module_path, temp_cwd
80
+ from utilities.permissions import Permissions
81
+ from utilities.platform import IS_LINUX
82
+ from utilities.sentinel import Sentinel, is_sentinel, sentinel
89
83
  from utilities.tempfile import TEMP_DIR, TemporaryDirectory
90
84
  from utilities.version import Version
91
- from utilities.zoneinfo import UTC
85
+ from utilities.whenever import (
86
+ DATE_DELTA_MAX,
87
+ DATE_DELTA_MIN,
88
+ DATE_DELTA_PARSABLE_MAX,
89
+ DATE_DELTA_PARSABLE_MIN,
90
+ DATE_TIME_DELTA_MAX,
91
+ DATE_TIME_DELTA_MIN,
92
+ DATE_TIME_DELTA_PARSABLE_MAX,
93
+ DATE_TIME_DELTA_PARSABLE_MIN,
94
+ DATE_TWO_DIGIT_YEAR_MAX,
95
+ DATE_TWO_DIGIT_YEAR_MIN,
96
+ DAY,
97
+ TIME_DELTA_MAX,
98
+ TIME_DELTA_MIN,
99
+ DatePeriod,
100
+ TimePeriod,
101
+ ZonedDateTimePeriod,
102
+ get_now,
103
+ to_date_time_delta,
104
+ to_days,
105
+ to_nanoseconds,
106
+ )
107
+ from utilities.zoneinfo import UTC, to_zone_info
92
108
 
93
109
  if TYPE_CHECKING:
94
- from collections.abc import Collection, Hashable, Iterable, Iterator, Sequence
110
+ from collections.abc import Collection, Hashable, Iterable, Iterator
95
111
  from zoneinfo import ZoneInfo
96
112
 
97
113
  from hypothesis.database import ExampleDatabase
114
+ from libcst import Import, ImportFrom
98
115
  from numpy.random import RandomState
99
- from sqlalchemy.ext.asyncio import AsyncEngine
116
+ from sqlalchemy import URL
100
117
 
101
118
  from utilities.numpy import NDArrayB, NDArrayF, NDArrayI, NDArrayO
102
- from utilities.sqlalchemy import Dialect, TableOrORMInstOrClass
103
- from utilities.types import Duration, Number, RoundMode
119
+ from utilities.types import Number, TimeZone, TimeZoneLike
104
120
 
105
121
 
106
- _T = TypeVar("_T")
107
122
  type MaybeSearchStrategy[_T] = _T | SearchStrategy[_T]
108
123
  type Shape = int | tuple[int, ...]
109
124
 
@@ -161,175 +176,184 @@ def bool_arrays(
161
176
 
162
177
 
163
178
  @composite
164
- def date_durations(
179
+ def date_deltas(
165
180
  draw: DrawFn,
166
181
  /,
167
182
  *,
168
- min_int: MaybeSearchStrategy[int | None] = None,
169
- max_int: MaybeSearchStrategy[int | None] = None,
170
- min_timedelta: MaybeSearchStrategy[dt.timedelta | None] = None,
171
- max_timedelta: MaybeSearchStrategy[dt.timedelta | None] = None,
172
- two_way: bool = False,
173
- ) -> Duration:
174
- """Strategy for generating datetime durations."""
175
- min_int_, max_int_ = [draw2(draw, v) for v in [min_int, max_int]]
176
- min_timedelta_, max_timedelta_ = [
177
- draw2(draw, v) for v in [min_timedelta, max_timedelta]
178
- ]
179
- min_parts: Sequence[dt.timedelta | None] = [dt.timedelta.min, min_timedelta_]
180
- if min_int_ is not None:
181
- with assume_does_not_raise(OverflowError):
182
- min_parts.append(date_duration_to_timedelta(min_int_))
183
- if two_way:
184
- from utilities.whenever import MIN_SERIALIZABLE_TIMEDELTA
185
-
186
- min_parts.append(MIN_SERIALIZABLE_TIMEDELTA)
187
- min_timedelta_use = max_nullable(min_parts)
188
- max_parts: Sequence[dt.timedelta | None] = [dt.timedelta.max, max_timedelta_]
189
- if max_int_ is not None:
190
- with assume_does_not_raise(OverflowError):
191
- max_parts.append(date_duration_to_timedelta(max_int_))
192
- if two_way:
193
- from utilities.whenever import MAX_SERIALIZABLE_TIMEDELTA
194
-
195
- max_parts.append(MAX_SERIALIZABLE_TIMEDELTA)
196
- max_timedelta_use = min_nullable(max_parts)
197
- _ = assume(min_timedelta_use <= max_timedelta_use)
198
- st_timedeltas = (
199
- timedeltas(min_value=min_timedelta_use, max_value=max_timedelta_use)
200
- .map(_round_timedelta)
201
- .filter(
202
- partial(
203
- _is_between_timedelta, min_=min_timedelta_use, max_=max_timedelta_use
204
- )
205
- )
206
- )
207
- st_integers = st_timedeltas.map(date_duration_to_int)
208
- st_floats = st_integers.map(float)
209
- return draw(st_integers | st_floats | st_timedeltas)
183
+ min_value: MaybeSearchStrategy[DateDelta | None] = None,
184
+ max_value: MaybeSearchStrategy[DateDelta | None] = None,
185
+ parsable: MaybeSearchStrategy[bool] = False,
186
+ ) -> DateDelta:
187
+ """Strategy for generating date deltas."""
188
+ min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
189
+ match min_value_:
190
+ case None:
191
+ min_value_ = DATE_DELTA_MIN
192
+ case DateDelta():
193
+ ...
194
+ case never:
195
+ assert_never(never)
196
+ match max_value_:
197
+ case None:
198
+ max_value_ = DATE_DELTA_MAX
199
+ case DateDelta():
200
+ ...
201
+ case never:
202
+ assert_never(never)
203
+ min_days = to_days(min_value_)
204
+ max_days = to_days(max_value_)
205
+ if draw2(draw, parsable):
206
+ min_days = max(min_days, to_days(DATE_DELTA_PARSABLE_MIN))
207
+ max_days = min(max_days, to_days(DATE_DELTA_PARSABLE_MAX))
208
+ days = draw(integers(min_value=min_days, max_value=max_days))
209
+ return DateDelta(days=days)
210
210
 
211
211
 
212
- def _round_timedelta(timedelta: dt.timedelta, /) -> dt.timedelta:
213
- return dt.timedelta(days=timedelta.days)
212
+ ##
214
213
 
215
214
 
216
- def _is_between_timedelta(
217
- timedelta: dt.timedelta, /, *, min_: dt.timedelta, max_: dt.timedelta
218
- ) -> bool:
219
- return min_ <= timedelta <= max_
215
+ @composite
216
+ def date_periods(
217
+ draw: DrawFn,
218
+ /,
219
+ *,
220
+ min_value: MaybeSearchStrategy[Date | None] = None,
221
+ max_value: MaybeSearchStrategy[Date | None] = None,
222
+ two_digit: MaybeSearchStrategy[bool] = False,
223
+ ) -> DatePeriod:
224
+ """Strategy for generating date periods."""
225
+ min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
226
+ two_digit_ = draw2(draw, two_digit)
227
+ strategy = dates(min_value=min_value_, max_value=max_value_, two_digit=two_digit_)
228
+ start, end = draw(pairs(strategy, sorted=True))
229
+ return DatePeriod(start, end)
220
230
 
221
231
 
222
232
  ##
223
233
 
224
234
 
225
235
  @composite
226
- def dates_two_digit_year(
236
+ def date_time_deltas(
227
237
  draw: DrawFn,
228
238
  /,
229
239
  *,
230
- min_value: MaybeSearchStrategy[dt.date] = MIN_DATE_TWO_DIGIT_YEAR,
231
- max_value: MaybeSearchStrategy[dt.date] = MAX_DATE_TWO_DIGIT_YEAR,
232
- ) -> dt.date:
233
- """Strategy for generating dates with valid 2 digit years."""
240
+ min_value: MaybeSearchStrategy[DateTimeDelta | None] = None,
241
+ max_value: MaybeSearchStrategy[DateTimeDelta | None] = None,
242
+ parsable: MaybeSearchStrategy[bool] = False,
243
+ nativable: MaybeSearchStrategy[bool] = False,
244
+ ) -> DateTimeDelta:
245
+ """Strategy for generating date deltas."""
234
246
  min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
235
- min_value_ = max(min_value_, MIN_DATE_TWO_DIGIT_YEAR)
236
- max_value_ = min(max_value_, MAX_DATE_TWO_DIGIT_YEAR)
237
- return draw(dates(min_value=min_value_, max_value=max_value_))
247
+ match min_value_:
248
+ case None:
249
+ min_value_ = DATE_TIME_DELTA_MIN
250
+ case DateTimeDelta():
251
+ ...
252
+ case never:
253
+ assert_never(never)
254
+ match max_value_:
255
+ case None:
256
+ max_value_ = DATE_TIME_DELTA_MAX
257
+ case DateTimeDelta():
258
+ ...
259
+ case never:
260
+ assert_never(never)
261
+ min_nanos, max_nanos = map(to_nanoseconds, [min_value_, max_value_])
262
+ if draw2(draw, parsable):
263
+ min_nanos = max(min_nanos, to_nanoseconds(DATE_TIME_DELTA_PARSABLE_MIN))
264
+ max_nanos = min(max_nanos, to_nanoseconds(DATE_TIME_DELTA_PARSABLE_MAX))
265
+ if draw2(draw, nativable):
266
+ min_micros, _ = divmod(min_nanos, 1000)
267
+ max_micros, _ = divmod(max_nanos, 1000)
268
+ micros = draw(integers(min_value=min_micros + 1, max_value=max_micros))
269
+ nanos = 1000 * micros
270
+ else:
271
+ nanos = draw(integers(min_value=min_nanos, max_value=max_nanos))
272
+ return to_date_time_delta(nanos)
238
273
 
239
274
 
240
275
  ##
241
276
 
242
277
 
243
278
  @composite
244
- def datetime_durations(
279
+ def dates(
245
280
  draw: DrawFn,
246
281
  /,
247
282
  *,
248
- min_number: MaybeSearchStrategy[Number | None] = None,
249
- max_number: MaybeSearchStrategy[Number | None] = None,
250
- min_timedelta: MaybeSearchStrategy[dt.timedelta | None] = None,
251
- max_timedelta: MaybeSearchStrategy[dt.timedelta | None] = None,
252
- two_way: bool = False,
253
- ) -> Duration:
254
- """Strategy for generating datetime durations."""
255
- min_number_, max_number_ = [draw2(draw, v) for v in [min_number, max_number]]
256
- min_timedelta_, max_timedelta_ = [
257
- draw2(draw, v) for v in [min_timedelta, max_timedelta]
258
- ]
259
- min_parts = [min_timedelta_, dt.timedelta.min]
260
- if min_number_ is not None:
261
- with assume_does_not_raise(OverflowError):
262
- min_parts.append(datetime_duration_to_timedelta(min_number_))
263
- if two_way:
264
- from utilities.whenever import MIN_SERIALIZABLE_TIMEDELTA
265
-
266
- min_parts.append(MIN_SERIALIZABLE_TIMEDELTA)
267
- min_timedelta_use = max_nullable(min_parts)
268
- max_parts = [max_timedelta_, dt.timedelta.max]
269
- if max_number_ is not None:
270
- with assume_does_not_raise(OverflowError):
271
- max_parts.append(datetime_duration_to_timedelta(max_number_))
272
- if two_way:
273
- from utilities.whenever import MAX_SERIALIZABLE_TIMEDELTA
274
-
275
- max_parts.append(MAX_SERIALIZABLE_TIMEDELTA)
276
- max_timedelta_use = min_nullable(max_parts)
277
- _ = assume(min_timedelta_use <= max_timedelta_use)
278
- min_float_use, max_float_use = map(
279
- datetime_duration_to_float, [min_timedelta_use, max_timedelta_use]
280
- )
281
- _ = assume(min_float_use <= max_float_use)
282
- st_numbers = numbers(min_value=min_float_use, max_value=max_float_use)
283
- st_timedeltas = timedeltas(min_value=min_timedelta_use, max_value=max_timedelta_use)
284
- return draw(st_numbers | st_timedeltas)
283
+ min_value: MaybeSearchStrategy[Date | None] = None,
284
+ max_value: MaybeSearchStrategy[Date | None] = None,
285
+ two_digit: MaybeSearchStrategy[bool] = False,
286
+ ) -> Date:
287
+ """Strategy for generating dates."""
288
+ min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
289
+ match min_value_:
290
+ case None:
291
+ min_value_ = Date.MIN
292
+ case Date():
293
+ ...
294
+ case never:
295
+ assert_never(never)
296
+ match max_value_:
297
+ case None:
298
+ max_value_ = Date.MAX
299
+ case Date():
300
+ ...
301
+ case never:
302
+ assert_never(never)
303
+ if draw2(draw, two_digit):
304
+ min_value_ = max(min_value_, DATE_TWO_DIGIT_YEAR_MIN)
305
+ max_value_ = min(max_value_, DATE_TWO_DIGIT_YEAR_MAX)
306
+ min_date, max_date = [d.py_date() for d in [min_value_, max_value_]]
307
+ py_date = draw(hypothesis.strategies.dates(min_value=min_date, max_value=max_date))
308
+ return Date.from_py_date(py_date)
285
309
 
286
310
 
287
311
  ##
288
312
 
289
313
 
290
314
  @overload
291
- def draw2(
315
+ def draw2[T](
292
316
  data_or_draw: DataObject | DrawFn,
293
- maybe_strategy: MaybeSearchStrategy[_T],
317
+ maybe_strategy: MaybeSearchStrategy[T],
294
318
  /,
295
319
  *,
296
320
  sentinel: bool = False,
297
- ) -> _T: ...
321
+ ) -> T: ...
298
322
  @overload
299
- def draw2(
323
+ def draw2[T](
300
324
  data_or_draw: DataObject | DrawFn,
301
- maybe_strategy: MaybeSearchStrategy[_T | None | Sentinel],
302
- default: SearchStrategy[_T | None],
325
+ maybe_strategy: MaybeSearchStrategy[T | None | Sentinel],
326
+ default: SearchStrategy[T | None],
303
327
  /,
304
328
  *,
305
329
  sentinel: Literal[True],
306
- ) -> _T | None: ...
330
+ ) -> T | None: ...
307
331
  @overload
308
- def draw2(
332
+ def draw2[T](
309
333
  data_or_draw: DataObject | DrawFn,
310
- maybe_strategy: MaybeSearchStrategy[_T | None],
311
- default: SearchStrategy[_T],
334
+ maybe_strategy: MaybeSearchStrategy[T | None],
335
+ default: SearchStrategy[T],
312
336
  /,
313
337
  *,
314
338
  sentinel: Literal[False] = False,
315
- ) -> _T: ...
339
+ ) -> T: ...
316
340
  @overload
317
- def draw2(
341
+ def draw2[T](
318
342
  data_or_draw: DataObject | DrawFn,
319
- maybe_strategy: MaybeSearchStrategy[_T | None | Sentinel],
320
- default: SearchStrategy[_T] | None = None,
343
+ maybe_strategy: MaybeSearchStrategy[T | None | Sentinel],
344
+ default: SearchStrategy[T] | None = None,
321
345
  /,
322
346
  *,
323
347
  sentinel: bool = False,
324
- ) -> _T | None: ...
325
- def draw2(
348
+ ) -> T | None: ...
349
+ def draw2[T](
326
350
  data_or_draw: DataObject | DrawFn,
327
- maybe_strategy: MaybeSearchStrategy[_T | None | Sentinel],
328
- default: SearchStrategy[_T | None] | None = None,
351
+ maybe_strategy: MaybeSearchStrategy[T | None | Sentinel],
352
+ default: SearchStrategy[T | None] | None = None,
329
353
  /,
330
354
  *,
331
355
  sentinel: bool = False,
332
- ) -> _T | None:
356
+ ) -> T | None:
333
357
  """Draw an element from a strategy, unless you require it to be non-nullable."""
334
358
  draw = data_or_draw.draw if isinstance(data_or_draw, DataObject) else data_or_draw
335
359
  if isinstance(maybe_strategy, SearchStrategy):
@@ -343,21 +367,21 @@ def draw2(
343
367
  return value
344
368
  case None, SearchStrategy(), False:
345
369
  value2 = draw(default)
346
- if isinstance(value2, Sentinel):
370
+ if is_sentinel(value2):
347
371
  raise _Draw2DefaultGeneratedSentinelError
348
372
  return value2
349
373
  case Sentinel(), None, _:
350
374
  raise _Draw2InputResolvedToSentinelError
351
375
  case Sentinel(), SearchStrategy(), True:
352
376
  value2 = draw(default)
353
- if isinstance(value2, Sentinel):
377
+ if is_sentinel(value2):
354
378
  raise _Draw2DefaultGeneratedSentinelError
355
379
  return value2
356
380
  case Sentinel(), SearchStrategy(), False:
357
381
  raise _Draw2InputResolvedToSentinelError
358
382
  case _, _, _:
359
383
  return value
360
- case _ as never:
384
+ case never:
361
385
  assert_never(never)
362
386
 
363
387
 
@@ -387,16 +411,27 @@ def float32s(
387
411
  draw: DrawFn,
388
412
  /,
389
413
  *,
390
- min_value: MaybeSearchStrategy[float] = MIN_FLOAT32,
391
- max_value: MaybeSearchStrategy[float] = MAX_FLOAT32,
414
+ min_value: MaybeSearchStrategy[float | None] = None,
415
+ max_value: MaybeSearchStrategy[float | None] = None,
416
+ exclude_min: MaybeSearchStrategy[bool] = False,
417
+ exclude_max: MaybeSearchStrategy[bool] = False,
392
418
  ) -> float:
393
419
  """Strategy for generating float32s."""
394
420
  min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
395
- min_value_ = max(min_value_, MIN_FLOAT32)
396
- max_value_ = min(max_value_, MAX_FLOAT32)
421
+ min_value_ = max_nullable([min_value_, MIN_FLOAT32])
422
+ max_value_ = min_nullable([max_value_, MAX_FLOAT32])
397
423
  if is_zero(min_value_) and is_zero(max_value_):
398
424
  min_value_ = max_value_ = 0.0
399
- return draw(floats(min_value_, max_value_, width=32))
425
+ exclude_min_, exclude_max_ = [draw2(draw, e) for e in [exclude_min, exclude_max]]
426
+ return draw(
427
+ floats(
428
+ min_value_,
429
+ max_value_,
430
+ width=32,
431
+ exclude_min=exclude_min_,
432
+ exclude_max=exclude_max_,
433
+ )
434
+ )
400
435
 
401
436
 
402
437
  @composite
@@ -404,16 +439,27 @@ def float64s(
404
439
  draw: DrawFn,
405
440
  /,
406
441
  *,
407
- min_value: MaybeSearchStrategy[float] = MIN_FLOAT64,
408
- max_value: MaybeSearchStrategy[float] = MAX_FLOAT64,
442
+ min_value: MaybeSearchStrategy[float | None] = None,
443
+ max_value: MaybeSearchStrategy[float | None] = None,
444
+ exclude_min: MaybeSearchStrategy[bool] = False,
445
+ exclude_max: MaybeSearchStrategy[bool] = False,
409
446
  ) -> float:
410
447
  """Strategy for generating float64s."""
411
448
  min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
412
- min_value_ = max(min_value_, MIN_FLOAT64)
413
- max_value_ = min(max_value_, MAX_FLOAT64)
449
+ min_value_ = max_nullable([min_value_, MIN_FLOAT64])
450
+ max_value_ = min_nullable([max_value_, MAX_FLOAT64])
414
451
  if is_zero(min_value_) and is_zero(max_value_):
415
452
  min_value_ = max_value_ = 0.0
416
- return draw(floats(min_value_, max_value_, width=64))
453
+ exclude_min_, exclude_max_ = [draw2(draw, e) for e in [exclude_min, exclude_max]]
454
+ return draw(
455
+ floats(
456
+ min_value_,
457
+ max_value_,
458
+ width=64,
459
+ exclude_min=exclude_min_,
460
+ exclude_max=exclude_max_,
461
+ )
462
+ )
417
463
 
418
464
 
419
465
  ##
@@ -533,6 +579,59 @@ def hashables() -> SearchStrategy[Hashable]:
533
579
  ##
534
580
 
535
581
 
582
+ @composite
583
+ def import_froms(
584
+ draw: DrawFn,
585
+ /,
586
+ *,
587
+ min_depth: MaybeSearchStrategy[int | None] = None,
588
+ max_depth: MaybeSearchStrategy[int | None] = None,
589
+ ) -> ImportFrom:
590
+ """Strategy for generating import-froms."""
591
+ from utilities.libcst import generate_import_from
592
+
593
+ min_depth_, max_depth_ = [draw2(draw, d) for d in [min_depth, max_depth]]
594
+ path = draw(
595
+ paths(
596
+ min_depth=1 if min_depth_ is None else max(min_depth_, 1),
597
+ max_depth=max_depth_,
598
+ )
599
+ )
600
+ module = module_path(path)
601
+ name = draw(text_ascii(min_size=1))
602
+ asname = draw(text_ascii(min_size=1) | none())
603
+ return generate_import_from(module, name, asname=asname)
604
+
605
+
606
+ ##
607
+
608
+
609
+ @composite
610
+ def imports(
611
+ draw: DrawFn,
612
+ /,
613
+ *,
614
+ min_depth: MaybeSearchStrategy[int | None] = None,
615
+ max_depth: MaybeSearchStrategy[int | None] = None,
616
+ ) -> Import:
617
+ """Strategy for generating imports."""
618
+ from utilities.libcst import generate_import
619
+
620
+ min_depth_, max_depth_ = [draw2(draw, d) for d in [min_depth, max_depth]]
621
+ path = draw(
622
+ paths(
623
+ min_depth=1 if min_depth_ is None else max(min_depth_, 1),
624
+ max_depth=max_depth_,
625
+ )
626
+ )
627
+ module = module_path(path)
628
+ asname = draw(text_ascii(min_size=1) | none())
629
+ return generate_import(module, asname=asname)
630
+
631
+
632
+ ##
633
+
634
+
536
635
  @composite
537
636
  def int_arrays(
538
637
  draw: DrawFn,
@@ -562,18 +661,48 @@ def int_arrays(
562
661
  ##
563
662
 
564
663
 
664
+ @composite
665
+ def int8s(
666
+ draw: DrawFn,
667
+ /,
668
+ *,
669
+ min_value: MaybeSearchStrategy[int | None] = None,
670
+ max_value: MaybeSearchStrategy[int | None] = None,
671
+ ) -> int:
672
+ """Strategy for generating int8s."""
673
+ min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
674
+ min_value_ = max_nullable([min_value_, MIN_INT8])
675
+ max_value_ = min_nullable([max_value_, MAX_INT8])
676
+ return draw(integers(min_value=min_value_, max_value=max_value_))
677
+
678
+
679
+ @composite
680
+ def int16s(
681
+ draw: DrawFn,
682
+ /,
683
+ *,
684
+ min_value: MaybeSearchStrategy[int | None] = None,
685
+ max_value: MaybeSearchStrategy[int | None] = None,
686
+ ) -> int:
687
+ """Strategy for generating int16s."""
688
+ min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
689
+ min_value_ = max_nullable([min_value_, MIN_INT16])
690
+ max_value_ = min_nullable([max_value_, MAX_INT16])
691
+ return draw(integers(min_value=min_value_, max_value=max_value_))
692
+
693
+
565
694
  @composite
566
695
  def int32s(
567
696
  draw: DrawFn,
568
697
  /,
569
698
  *,
570
- min_value: MaybeSearchStrategy[int] = MIN_INT32,
571
- max_value: MaybeSearchStrategy[int] = MAX_INT32,
699
+ min_value: MaybeSearchStrategy[int | None] = None,
700
+ max_value: MaybeSearchStrategy[int | None] = None,
572
701
  ) -> int:
573
702
  """Strategy for generating int32s."""
574
703
  min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
575
- min_value_ = max(min_value_, MIN_INT32)
576
- max_value_ = min(max_value_, MAX_INT32)
704
+ min_value_ = max_nullable([min_value_, MIN_INT32])
705
+ max_value_ = min_nullable([max_value_, MAX_INT32])
577
706
  return draw(integers(min_value_, max_value_))
578
707
 
579
708
 
@@ -582,13 +711,13 @@ def int64s(
582
711
  draw: DrawFn,
583
712
  /,
584
713
  *,
585
- min_value: MaybeSearchStrategy[int] = MIN_INT64,
586
- max_value: MaybeSearchStrategy[int] = MAX_INT64,
714
+ min_value: MaybeSearchStrategy[int | None] = None,
715
+ max_value: MaybeSearchStrategy[int | None] = None,
587
716
  ) -> int:
588
717
  """Strategy for generating int64s."""
589
718
  min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
590
- min_value_ = max(min_value_, MIN_INT64)
591
- max_value_ = min(max_value_, MAX_INT64)
719
+ min_value_ = max_nullable([min_value_, MIN_INT64])
720
+ max_value_ = min_nullable([max_value_, MAX_INT64])
592
721
  return draw(integers(min_value_, max_value_))
593
722
 
594
723
 
@@ -596,15 +725,15 @@ def int64s(
596
725
 
597
726
 
598
727
  @composite
599
- def lists_fixed_length(
728
+ def lists_fixed_length[T](
600
729
  draw: DrawFn,
601
- strategy: SearchStrategy[_T],
730
+ strategy: SearchStrategy[T],
602
731
  size: MaybeSearchStrategy[int],
603
732
  /,
604
733
  *,
605
734
  unique: MaybeSearchStrategy[bool] = False,
606
735
  sorted: MaybeSearchStrategy[bool] = False, # noqa: A002
607
- ) -> list[_T]:
736
+ ) -> list[T]:
608
737
  """Strategy for generating lists of a fixed length."""
609
738
  size_ = draw2(draw, size)
610
739
  elements = draw(
@@ -619,243 +748,32 @@ def lists_fixed_length(
619
748
 
620
749
 
621
750
  @composite
622
- def min_and_max_datetimes(
623
- draw: DrawFn,
624
- /,
625
- *,
626
- min_value: MaybeSearchStrategy[dt.datetime | None] = None,
627
- max_value: MaybeSearchStrategy[dt.datetime | None] = None,
628
- time_zone: MaybeSearchStrategy[ZoneInfo | timezone] = UTC,
629
- round_: RoundMode | None = None,
630
- timedelta: dt.timedelta | None = None,
631
- rel_tol: float | None = None,
632
- abs_tol: float | None = None,
633
- valid: bool = False,
634
- ) -> tuple[dt.datetime, dt.datetime]:
635
- """Strategy for generating min/max datetimes."""
636
- match min_value, max_value:
637
- case None, None:
638
- return draw(
639
- pairs(
640
- zoned_datetimes(
641
- time_zone=time_zone,
642
- round_=round_,
643
- timedelta=timedelta,
644
- rel_tol=rel_tol,
645
- abs_tol=abs_tol,
646
- valid=valid,
647
- ),
648
- sorted=True,
649
- )
650
- )
651
- case None, dt.datetime():
652
- min_value_ = draw(
653
- zoned_datetimes(
654
- max_value=max_value,
655
- time_zone=time_zone,
656
- round_=round_,
657
- timedelta=timedelta,
658
- rel_tol=rel_tol,
659
- abs_tol=abs_tol,
660
- valid=valid,
661
- )
662
- )
663
- return min_value_, max_value
664
- case dt.datetime(), None:
665
- max_value_ = draw(
666
- zoned_datetimes(
667
- min_value=min_value,
668
- time_zone=time_zone,
669
- round_=round_,
670
- timedelta=timedelta,
671
- rel_tol=rel_tol,
672
- abs_tol=abs_tol,
673
- valid=valid,
674
- )
675
- )
676
- return min_value, max_value_
677
- case dt.datetime(), dt.datetime():
678
- _ = assume(min_value <= max_value)
679
- return min_value, max_value
680
- case _, _:
681
- strategy = zoned_datetimes(
682
- time_zone=time_zone,
683
- round_=round_,
684
- timedelta=timedelta,
685
- rel_tol=rel_tol,
686
- abs_tol=abs_tol,
687
- valid=valid,
688
- )
689
- min_value_ = draw2(draw, min_value, strategy)
690
- max_value_ = draw2(draw, max_value, strategy)
691
- _ = assume(min_value_ <= max_value_)
692
- return min_value_, max_value_
693
- case _ as never:
694
- assert_never(never)
695
-
696
-
697
- ##
698
-
699
-
700
- @composite
701
- def min_and_maybe_max_datetimes(
751
+ def month_days(
702
752
  draw: DrawFn,
703
753
  /,
704
754
  *,
705
- min_value: MaybeSearchStrategy[dt.datetime | None] = None,
706
- max_value: MaybeSearchStrategy[dt.datetime | None | Sentinel] = sentinel,
707
- time_zone: MaybeSearchStrategy[ZoneInfo | timezone] = UTC,
708
- round_: RoundMode | None = None,
709
- timedelta: dt.timedelta | None = None,
710
- rel_tol: float | None = None,
711
- abs_tol: float | None = None,
712
- valid: bool = False,
713
- ) -> tuple[dt.datetime, dt.datetime | None]:
714
- match min_value, max_value:
715
- case None, Sentinel():
716
- min_value_, max_value_ = draw(
717
- pairs(
718
- zoned_datetimes(
719
- time_zone=time_zone,
720
- round_=round_,
721
- timedelta=timedelta,
722
- rel_tol=rel_tol,
723
- abs_tol=abs_tol,
724
- valid=valid,
725
- ),
726
- sorted=True,
727
- )
728
- )
729
- return min_value_, draw(just(max_value_) | none())
730
- case None, None:
731
- min_value_ = draw(
732
- zoned_datetimes(
733
- time_zone=time_zone,
734
- round_=round_,
735
- timedelta=timedelta,
736
- rel_tol=rel_tol,
737
- abs_tol=abs_tol,
738
- valid=valid,
739
- )
740
- )
741
- return min_value_, None
742
- case None, dt.datetime():
743
- min_value_ = draw(
744
- zoned_datetimes(
745
- max_value=max_value,
746
- time_zone=time_zone,
747
- round_=round_,
748
- timedelta=timedelta,
749
- rel_tol=rel_tol,
750
- abs_tol=abs_tol,
751
- valid=valid,
752
- )
753
- )
754
- return min_value_, max_value
755
- case dt.datetime(), Sentinel():
756
- max_value_ = draw(
757
- zoned_datetimes(
758
- min_value=min_value,
759
- time_zone=time_zone,
760
- round_=round_,
761
- timedelta=timedelta,
762
- rel_tol=rel_tol,
763
- abs_tol=abs_tol,
764
- valid=valid,
765
- )
766
- | none()
767
- )
768
- return min_value, max_value_
769
- case dt.datetime(), None:
770
- return min_value, None
771
- case dt.datetime(), dt.datetime():
772
- _ = assume(min_value <= max_value)
773
- return min_value, max_value
774
- case _, _:
775
- strategy = zoned_datetimes(
776
- time_zone=time_zone,
777
- round_=round_,
778
- timedelta=timedelta,
779
- rel_tol=rel_tol,
780
- abs_tol=abs_tol,
781
- valid=valid,
782
- )
783
- min_value_ = draw2(draw, min_value, strategy)
784
- max_value_ = draw2(draw, max_value, strategy | none(), sentinel=True)
785
- _ = assume((max_value_ is None) or (min_value_ <= max_value_))
786
- return min_value_, max_value_
787
- case _ as never:
755
+ min_value: MaybeSearchStrategy[MonthDay | None] = None,
756
+ max_value: MaybeSearchStrategy[MonthDay | None] = None,
757
+ ) -> MonthDay:
758
+ """Strategy for generating month-days."""
759
+ min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
760
+ match min_value_:
761
+ case None:
762
+ min_value_ = MonthDay.MIN
763
+ case MonthDay():
764
+ ...
765
+ case never:
788
766
  assert_never(never)
789
-
790
-
791
- ##
792
-
793
-
794
- @composite
795
- def min_and_maybe_max_sizes(
796
- draw: DrawFn,
797
- /,
798
- *,
799
- min_value: MaybeSearchStrategy[int | None] = None,
800
- max_value: MaybeSearchStrategy[int | None | Sentinel] = sentinel,
801
- ) -> tuple[int, int | None]:
802
- match min_value, max_value:
803
- case None, Sentinel():
804
- min_value_, max_value_ = draw(pairs(integers(min_value=0), sorted=True))
805
- return min_value_, draw(just(max_value_) | none())
806
- case None, None:
807
- min_value_ = draw(integers(min_value=0))
808
- return min_value_, None
809
- case None, int():
810
- min_value_ = draw(integers(0, max_value))
811
- return min_value_, max_value
812
- case int(), Sentinel():
813
- max_value_ = draw(integers(min_value=min_value) | none())
814
- return min_value, max_value_
815
- case int(), None:
816
- return min_value, None
817
- case int(), int():
818
- _ = assume(min_value <= max_value)
819
- return min_value, max_value
820
- case _, _:
821
- strategy = integers(min_value=0)
822
- min_value_ = draw2(draw, min_value, strategy)
823
- max_value_ = draw2(draw, max_value, strategy | none(), sentinel=True)
824
- _ = assume((max_value_ is None) or (min_value_ <= max_value_))
825
- return min_value_, max_value_
826
- case _ as never:
767
+ match max_value_:
768
+ case None:
769
+ max_value_ = MonthDay.MAX
770
+ case MonthDay():
771
+ ...
772
+ case never:
827
773
  assert_never(never)
828
-
829
-
830
- ##
831
-
832
-
833
- @composite
834
- def months(
835
- draw: DrawFn,
836
- /,
837
- *,
838
- min_value: MaybeSearchStrategy[Month] = MIN_MONTH,
839
- max_value: MaybeSearchStrategy[Month] = MAX_MONTH,
840
- ) -> Month:
841
- """Strategy for generating datetimes with the UTC timezone."""
842
- min_value_, max_value_ = [draw2(draw, v).to_date() for v in [min_value, max_value]]
843
- date = draw(dates(min_value=min_value_, max_value=max_value_))
844
- return date_to_month(date)
845
-
846
-
847
- ##
848
-
849
-
850
- @composite
851
- def namespace_mixins(draw: DrawFn, /) -> type:
852
- """Strategy for generating task namespace mixins."""
853
- path = draw(temp_paths())
854
-
855
- class NamespaceMixin:
856
- task_namespace = path.name
857
-
858
- return NamespaceMixin
774
+ min_date, max_date = [m.in_year(2000) for m in [min_value_, max_value_]]
775
+ date = draw(dates(min_value=min_date, max_value=max_date))
776
+ return date.month_day()
859
777
 
860
778
 
861
779
  ##
@@ -901,18 +819,18 @@ def numbers(
901
819
  ##
902
820
 
903
821
 
904
- def pairs(
905
- strategy: SearchStrategy[_T],
822
+ def pairs[T](
823
+ strategy: SearchStrategy[T],
906
824
  /,
907
825
  *,
908
826
  unique: MaybeSearchStrategy[bool] = False,
909
827
  sorted: MaybeSearchStrategy[bool] = False, # noqa: A002
910
- ) -> SearchStrategy[tuple[_T, _T]]:
828
+ ) -> SearchStrategy[tuple[T, T]]:
911
829
  """Strategy for generating pairs of elements."""
912
830
  return lists_fixed_length(strategy, 2, unique=unique, sorted=sorted).map(_pairs_map)
913
831
 
914
832
 
915
- def _pairs_map(elements: list[_T], /) -> tuple[_T, _T]:
833
+ def _pairs_map[T](elements: list[T], /) -> tuple[T, T]:
916
834
  first, second = elements
917
835
  return first, second
918
836
 
@@ -920,48 +838,135 @@ def _pairs_map(elements: list[_T], /) -> tuple[_T, _T]:
920
838
  ##
921
839
 
922
840
 
923
- def paths() -> SearchStrategy[Path]:
841
+ @composite
842
+ def paths(
843
+ draw: DrawFn,
844
+ /,
845
+ *,
846
+ min_depth: MaybeSearchStrategy[int | None] = None,
847
+ max_depth: MaybeSearchStrategy[int | None] = None,
848
+ ) -> Path:
924
849
  """Strategy for generating `Path`s."""
925
- reserved = {"NUL"}
926
- strategy = text_ascii(min_size=1, max_size=10).filter(lambda x: x not in reserved)
927
- return lists(strategy, max_size=10).map(lambda parts: Path(*parts))
850
+ min_depth_, max_depth_ = [draw2(draw, d) for d in [min_depth, max_depth]]
851
+ min_depth_ = max_nullable([min_depth_, 0])
852
+ max_depth_ = min_nullable([max_depth_, 10])
853
+ parts = draw(lists(_path_parts(), min_size=min_depth_, max_size=max_depth_))
854
+ return Path(*parts)
855
+
856
+
857
+ @composite
858
+ def _path_parts(draw: DrawFn, /) -> str:
859
+ part = draw(text_ascii(min_size=1, max_size=10))
860
+ reserved = {"AUX", "NUL", "nuL", "pRn"}
861
+ _ = assume(part not in reserved)
862
+ return part
928
863
 
929
864
 
930
865
  ##
931
866
 
932
867
 
933
868
  @composite
934
- def plain_datetimes(
869
+ def permissions(
935
870
  draw: DrawFn,
936
871
  /,
937
872
  *,
938
- min_value: MaybeSearchStrategy[dt.datetime] = DATETIME_MIN_NAIVE,
939
- max_value: MaybeSearchStrategy[dt.datetime] = DATETIME_MAX_NAIVE,
940
- round_: RoundMode | None = None,
941
- timedelta: dt.timedelta | None = None,
942
- rel_tol: float | None = None,
943
- abs_tol: float | None = None,
944
- ) -> dt.datetime:
873
+ user_read: MaybeSearchStrategy[bool | None] = None,
874
+ user_write: MaybeSearchStrategy[bool | None] = None,
875
+ user_execute: MaybeSearchStrategy[bool | None] = None,
876
+ group_read: MaybeSearchStrategy[bool | None] = None,
877
+ group_write: MaybeSearchStrategy[bool | None] = None,
878
+ group_execute: MaybeSearchStrategy[bool | None] = None,
879
+ others_read: MaybeSearchStrategy[bool | None] = None,
880
+ others_write: MaybeSearchStrategy[bool | None] = None,
881
+ others_execute: MaybeSearchStrategy[bool | None] = None,
882
+ ) -> Permissions:
883
+ """Strategy for generating `Permissions`."""
884
+ return Permissions(
885
+ user_read=draw2(draw, user_read, booleans()),
886
+ user_write=draw2(draw, user_write, booleans()),
887
+ user_execute=draw2(draw, user_execute, booleans()),
888
+ group_read=draw2(draw, group_read, booleans()),
889
+ group_write=draw2(draw, group_write, booleans()),
890
+ group_execute=draw2(draw, group_execute, booleans()),
891
+ others_read=draw2(draw, others_read, booleans()),
892
+ others_write=draw2(draw, others_write, booleans()),
893
+ others_execute=draw2(draw, others_execute, booleans()),
894
+ )
895
+
896
+
897
+ ##
898
+
899
+
900
+ @composite
901
+ def plain_date_times(
902
+ draw: DrawFn,
903
+ /,
904
+ *,
905
+ min_value: MaybeSearchStrategy[PlainDateTime | None] = None,
906
+ max_value: MaybeSearchStrategy[PlainDateTime | None] = None,
907
+ ) -> PlainDateTime:
945
908
  """Strategy for generating plain datetimes."""
946
909
  min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
947
- datetime = draw(datetimes(min_value=min_value_, max_value=max_value_))
948
- if round_ is not None:
949
- if timedelta is None:
950
- raise PlainDateTimesError(round_=round_)
951
- datetime = round_datetime(
952
- datetime, timedelta, mode=round_, rel_tol=rel_tol, abs_tol=abs_tol
910
+ match min_value_:
911
+ case None:
912
+ min_value_ = PlainDateTime.MIN
913
+ case PlainDateTime():
914
+ ...
915
+ case never:
916
+ assert_never(never)
917
+ match max_value_:
918
+ case None:
919
+ max_value_ = PlainDateTime.MAX
920
+ case PlainDateTime():
921
+ ...
922
+ case never:
923
+ assert_never(never)
924
+ py_datetime = draw(
925
+ datetimes(
926
+ min_value=min_value_.py_datetime(), max_value=max_value_.py_datetime()
953
927
  )
954
- _ = assume(min_value_ <= datetime <= max_value_)
955
- return datetime
928
+ )
929
+ return PlainDateTime.from_py_datetime(py_datetime)
956
930
 
957
931
 
958
- @dataclass(kw_only=True, slots=True)
959
- class PlainDateTimesError(Exception):
960
- round_: RoundMode
932
+ ##
933
+
934
+
935
+ @composite
936
+ def py_datetimes(
937
+ draw: DrawFn, /, *, zoned: MaybeSearchStrategy[bool | None] = None
938
+ ) -> dt.datetime:
939
+ """Strategy for generating standard library datetimes."""
940
+ zoned_ = draw2(draw, zoned, booleans())
941
+ timezones = just(UTC) if zoned_ else none()
942
+ return draw(
943
+ hypothesis.strategies.datetimes(
944
+ min_value=dt.datetime(2000, 1, 1), # noqa: DTZ001
945
+ max_value=dt.datetime(2000, 12, 31), # noqa: DTZ001
946
+ timezones=timezones,
947
+ )
948
+ )
961
949
 
962
- @override
963
- def __str__(self) -> str:
964
- return "Rounding requires a timedelta; got None"
950
+
951
+ ##
952
+
953
+
954
+ def quadruples[T](
955
+ strategy: SearchStrategy[T],
956
+ /,
957
+ *,
958
+ unique: MaybeSearchStrategy[bool] = False,
959
+ sorted: MaybeSearchStrategy[bool] = False, # noqa: A002
960
+ ) -> SearchStrategy[tuple[T, T, T, T]]:
961
+ """Strategy for generating quadruples of elements."""
962
+ return lists_fixed_length(strategy, 4, unique=unique, sorted=sorted).map(
963
+ _quadruples_map
964
+ )
965
+
966
+
967
+ def _quadruples_map[T](elements: list[T], /) -> tuple[T, T, T, T]:
968
+ first, second, third, fourth = elements
969
+ return first, second, third, fourth
965
970
 
966
971
 
967
972
  ##
@@ -974,7 +979,7 @@ def random_states(
974
979
  """Strategy for generating `numpy` random states."""
975
980
  from numpy.random import RandomState
976
981
 
977
- seed_ = draw2(draw, seed, integers(0, MAX_UINT32))
982
+ seed_ = draw2(draw, seed, uint32s())
978
983
  return RandomState(seed=seed_)
979
984
 
980
985
 
@@ -990,9 +995,9 @@ def sentinels() -> SearchStrategy[Sentinel]:
990
995
 
991
996
 
992
997
  @composite
993
- def sets_fixed_length(
994
- draw: DrawFn, strategy: SearchStrategy[_T], size: MaybeSearchStrategy[int], /
995
- ) -> set[_T]:
998
+ def sets_fixed_length[T](
999
+ draw: DrawFn, strategy: SearchStrategy[T], size: MaybeSearchStrategy[int], /
1000
+ ) -> set[T]:
996
1001
  """Strategy for generating lists of a fixed length."""
997
1002
  size_ = draw2(draw, size)
998
1003
  return draw(sets(strategy, min_size=size_, max_size=size_))
@@ -1021,7 +1026,7 @@ def setup_hypothesis_profiles(
1021
1026
  return 100
1022
1027
  case Profile.ci:
1023
1028
  return 1000
1024
- case _ as never:
1029
+ case never:
1025
1030
  assert_never(never)
1026
1031
 
1027
1032
  @property
@@ -1031,11 +1036,11 @@ def setup_hypothesis_profiles(
1031
1036
  return Verbosity.quiet
1032
1037
  case Profile.ci:
1033
1038
  return Verbosity.verbose
1034
- case _ as never:
1039
+ case never:
1035
1040
  assert_never(never)
1036
1041
 
1037
1042
  phases = {Phase.explicit, Phase.reuse, Phase.generate, Phase.target}
1038
- if "HYPOTHESIS_NO_SHRINK" not in environ:
1043
+ if "HYPOTHESIS_NO_SHRINK" not in environ: # pragma: no cover
1039
1044
  phases.add(Phase.shrink)
1040
1045
  for profile in Profile:
1041
1046
  try:
@@ -1117,51 +1122,6 @@ def slices(
1117
1122
  ##
1118
1123
 
1119
1124
 
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
1125
  @composite
1166
1126
  def str_arrays(
1167
1127
  draw: DrawFn,
@@ -1201,9 +1161,7 @@ def temp_dirs(draw: DrawFn, /) -> TemporaryDirectory:
1201
1161
  """Search strategy for temporary directories."""
1202
1162
  _TEMP_DIR_HYPOTHESIS.mkdir(exist_ok=True)
1203
1163
  uuid = draw(uuids())
1204
- return TemporaryDirectory(
1205
- prefix=f"{uuid}__", dir=_TEMP_DIR_HYPOTHESIS, ignore_cleanup_errors=IS_WINDOWS
1206
- )
1164
+ return TemporaryDirectory(prefix=f"{uuid}__", dir=_TEMP_DIR_HYPOTHESIS)
1207
1165
 
1208
1166
 
1209
1167
  ##
@@ -1319,45 +1277,107 @@ def text_printable(
1319
1277
 
1320
1278
 
1321
1279
  @composite
1322
- def timedeltas_2w(
1280
+ def time_deltas(
1323
1281
  draw: DrawFn,
1324
1282
  /,
1325
1283
  *,
1326
- min_value: MaybeSearchStrategy[dt.timedelta] = dt.timedelta.min,
1327
- max_value: MaybeSearchStrategy[dt.timedelta] = dt.timedelta.max,
1328
- ) -> dt.timedelta:
1329
- """Strategy for generating timedeltas which can be se/deserialized."""
1330
- from utilities.whenever import (
1331
- MAX_SERIALIZABLE_TIMEDELTA,
1332
- MIN_SERIALIZABLE_TIMEDELTA,
1284
+ min_value: MaybeSearchStrategy[TimeDelta | None] = None,
1285
+ max_value: MaybeSearchStrategy[TimeDelta | None] = None,
1286
+ ) -> TimeDelta:
1287
+ """Strategy for generating time deltas."""
1288
+ min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
1289
+ match min_value_:
1290
+ case None:
1291
+ min_value_ = TIME_DELTA_MIN
1292
+ case TimeDelta():
1293
+ ...
1294
+ case never:
1295
+ assert_never(never)
1296
+ match max_value_:
1297
+ case None:
1298
+ max_value_ = TIME_DELTA_MAX
1299
+ case TimeDelta():
1300
+ ...
1301
+ case never:
1302
+ assert_never(never)
1303
+ py_time = draw(
1304
+ hypothesis.strategies.timedeltas(
1305
+ min_value=min_value_.py_timedelta(), max_value=max_value_.py_timedelta()
1306
+ )
1333
1307
  )
1308
+ return TimeDelta.from_py_timedelta(py_time)
1334
1309
 
1310
+
1311
+ ##
1312
+
1313
+
1314
+ @composite
1315
+ def time_periods(
1316
+ draw: DrawFn,
1317
+ /,
1318
+ *,
1319
+ min_value: MaybeSearchStrategy[Time | None] = None,
1320
+ max_value: MaybeSearchStrategy[Time | None] = None,
1321
+ ) -> TimePeriod:
1322
+ """Strategy for generating time periods."""
1335
1323
  min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
1336
- return draw(
1337
- timedeltas(
1338
- min_value=max(min_value_, MIN_SERIALIZABLE_TIMEDELTA),
1339
- max_value=min(max_value_, MAX_SERIALIZABLE_TIMEDELTA),
1324
+ strategy = times(min_value=min_value_, max_value=max_value_)
1325
+ start, end = draw(pairs(strategy, sorted=True))
1326
+ return TimePeriod(start, end)
1327
+
1328
+
1329
+ ##
1330
+
1331
+
1332
+ @composite
1333
+ def times(
1334
+ draw: DrawFn,
1335
+ /,
1336
+ *,
1337
+ min_value: MaybeSearchStrategy[Time | None] = None,
1338
+ max_value: MaybeSearchStrategy[Time | None] = None,
1339
+ ) -> Time:
1340
+ """Strategy for generating times."""
1341
+ min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
1342
+ match min_value_:
1343
+ case None:
1344
+ min_value_ = Time.MIN
1345
+ case Time():
1346
+ ...
1347
+ case never:
1348
+ assert_never(never)
1349
+ match max_value_:
1350
+ case None:
1351
+ max_value_ = Time.MAX
1352
+ case Time():
1353
+ ...
1354
+ case never:
1355
+ assert_never(never)
1356
+ py_time = draw(
1357
+ hypothesis.strategies.times(
1358
+ min_value=min_value_.py_time(), max_value=max_value_.py_time()
1340
1359
  )
1341
1360
  )
1361
+ return Time.from_py_time(py_time)
1342
1362
 
1343
1363
 
1344
1364
  ##
1345
1365
 
1346
1366
 
1347
- def triples(
1348
- strategy: SearchStrategy[_T],
1367
+ def triples[T](
1368
+ strategy: SearchStrategy[T],
1349
1369
  /,
1350
1370
  *,
1351
1371
  unique: MaybeSearchStrategy[bool] = False,
1352
1372
  sorted: MaybeSearchStrategy[bool] = False, # noqa: A002
1353
- ) -> SearchStrategy[tuple[_T, _T, _T]]:
1373
+ ) -> SearchStrategy[tuple[T, T, T]]:
1354
1374
  """Strategy for generating triples of elements."""
1355
1375
  return lists_fixed_length(strategy, 3, unique=unique, sorted=sorted).map(
1356
1376
  _triples_map
1357
1377
  )
1358
1378
 
1359
1379
 
1360
- def _triples_map(elements: list[_T], /) -> tuple[_T, _T, _T]:
1380
+ def _triples_map[T](elements: list[T], /) -> tuple[T, T, T]:
1361
1381
  first, second, third = elements
1362
1382
  return first, second, third
1363
1383
 
@@ -1365,18 +1385,48 @@ def _triples_map(elements: list[_T], /) -> tuple[_T, _T, _T]:
1365
1385
  ##
1366
1386
 
1367
1387
 
1388
+ @composite
1389
+ def uint8s(
1390
+ draw: DrawFn,
1391
+ /,
1392
+ *,
1393
+ min_value: MaybeSearchStrategy[int | None] = None,
1394
+ max_value: MaybeSearchStrategy[int | None] = None,
1395
+ ) -> int:
1396
+ """Strategy for generating uint8s."""
1397
+ min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
1398
+ min_value_ = max_nullable([min_value_, MIN_UINT8])
1399
+ max_value_ = min_nullable([max_value_, MAX_UINT8])
1400
+ return draw(integers(min_value=min_value_, max_value=max_value_))
1401
+
1402
+
1403
+ @composite
1404
+ def uint16s(
1405
+ draw: DrawFn,
1406
+ /,
1407
+ *,
1408
+ min_value: MaybeSearchStrategy[int | None] = None,
1409
+ max_value: MaybeSearchStrategy[int | None] = None,
1410
+ ) -> int:
1411
+ """Strategy for generating uint16s."""
1412
+ min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
1413
+ min_value_ = max_nullable([min_value_, MIN_UINT16])
1414
+ max_value_ = min_nullable([max_value_, MAX_UINT16])
1415
+ return draw(integers(min_value=min_value_, max_value=max_value_))
1416
+
1417
+
1368
1418
  @composite
1369
1419
  def uint32s(
1370
1420
  draw: DrawFn,
1371
1421
  /,
1372
1422
  *,
1373
- min_value: MaybeSearchStrategy[int] = MIN_UINT32,
1374
- max_value: MaybeSearchStrategy[int] = MAX_UINT32,
1423
+ min_value: MaybeSearchStrategy[int | None] = None,
1424
+ max_value: MaybeSearchStrategy[int | None] = None,
1375
1425
  ) -> int:
1376
1426
  """Strategy for generating uint32s."""
1377
1427
  min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
1378
- min_value_ = max(min_value_, MIN_UINT32)
1379
- max_value_ = min(max_value_, MAX_UINT32)
1428
+ min_value_ = max_nullable([min_value_, MIN_UINT32])
1429
+ max_value_ = min_nullable([max_value_, MAX_UINT32])
1380
1430
  return draw(integers(min_value=min_value_, max_value=max_value_))
1381
1431
 
1382
1432
 
@@ -1385,19 +1435,54 @@ def uint64s(
1385
1435
  draw: DrawFn,
1386
1436
  /,
1387
1437
  *,
1388
- min_value: MaybeSearchStrategy[int] = MIN_UINT64,
1389
- max_value: MaybeSearchStrategy[int] = MAX_UINT64,
1438
+ min_value: MaybeSearchStrategy[int | None] = None,
1439
+ max_value: MaybeSearchStrategy[int | None] = None,
1390
1440
  ) -> int:
1391
1441
  """Strategy for generating uint64s."""
1392
1442
  min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
1393
- min_value_ = max(min_value_, MIN_UINT64)
1394
- max_value_ = min(max_value_, MAX_UINT64)
1443
+ min_value_ = max_nullable([min_value_, MIN_UINT64])
1444
+ max_value_ = min_nullable([max_value_, MAX_UINT64])
1395
1445
  return draw(integers(min_value=min_value_, max_value=max_value_))
1396
1446
 
1397
1447
 
1398
1448
  ##
1399
1449
 
1400
1450
 
1451
+ @composite
1452
+ def urls(
1453
+ draw: DrawFn,
1454
+ /,
1455
+ *,
1456
+ all_: MaybeSearchStrategy[bool] = False,
1457
+ username: MaybeSearchStrategy[bool] = False,
1458
+ password: MaybeSearchStrategy[bool] = False,
1459
+ host: MaybeSearchStrategy[bool] = False,
1460
+ port: MaybeSearchStrategy[bool] = False,
1461
+ database: MaybeSearchStrategy[bool] = False,
1462
+ ) -> URL:
1463
+ from sqlalchemy import URL
1464
+
1465
+ have_all, have_username, have_password, have_host, have_port, have_database = [
1466
+ draw2(draw, b) for b in [all_, username, password, host, port, database]
1467
+ ]
1468
+ username_use = draw(text_ascii(min_size=1)) if have_all or have_username else None
1469
+ password_use = draw(text_ascii(min_size=1)) if have_all or have_password else None
1470
+ host_use = draw(text_ascii(min_size=1)) if have_all or have_host else None
1471
+ port_use = draw(integers(min_value=1)) if have_all or have_port else None
1472
+ database_use = draw(text_ascii(min_size=1)) if have_all or have_database else None
1473
+ return URL.create(
1474
+ drivername="sqlite",
1475
+ username=username_use,
1476
+ password=password_use,
1477
+ host=host_use,
1478
+ port=port_use,
1479
+ database=database_use,
1480
+ )
1481
+
1482
+
1483
+ ##
1484
+
1485
+
1401
1486
  @composite
1402
1487
  def versions(draw: DrawFn, /, *, suffix: MaybeSearchStrategy[bool] = False) -> Version:
1403
1488
  """Strategy for generating versions."""
@@ -1411,80 +1496,135 @@ def versions(draw: DrawFn, /, *, suffix: MaybeSearchStrategy[bool] = False) -> V
1411
1496
 
1412
1497
 
1413
1498
  @composite
1414
- def zoned_datetimes(
1499
+ def year_months(
1415
1500
  draw: DrawFn,
1416
1501
  /,
1417
1502
  *,
1418
- min_value: MaybeSearchStrategy[dt.datetime] = DATETIME_MIN_UTC + DAY,
1419
- max_value: MaybeSearchStrategy[dt.datetime] = DATETIME_MAX_UTC - DAY,
1420
- time_zone: MaybeSearchStrategy[ZoneInfo | timezone] = UTC,
1421
- round_: RoundMode | None = None,
1422
- timedelta: dt.timedelta | None = None,
1423
- rel_tol: float | None = None,
1424
- abs_tol: float | None = None,
1425
- valid: bool = False,
1426
- ) -> dt.datetime:
1427
- """Strategy for generating zoned datetimes."""
1428
- from utilities.whenever import (
1429
- CheckValidZonedDateTimeError,
1430
- check_valid_zoned_datetime,
1431
- )
1503
+ min_value: MaybeSearchStrategy[YearMonth | None] = None,
1504
+ max_value: MaybeSearchStrategy[YearMonth | None] = None,
1505
+ two_digit: MaybeSearchStrategy[bool] = False,
1506
+ ) -> YearMonth:
1507
+ """Strategy for generating months."""
1508
+ min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
1509
+ match min_value_:
1510
+ case None:
1511
+ min_value_ = YearMonth.MIN
1512
+ case YearMonth():
1513
+ ...
1514
+ case never:
1515
+ assert_never(never)
1516
+ match max_value_:
1517
+ case None:
1518
+ max_value_ = YearMonth.MAX
1519
+ case YearMonth():
1520
+ ...
1521
+ case never:
1522
+ assert_never(never)
1523
+ min_date, max_date = [m.on_day(1) for m in [min_value_, max_value_]]
1524
+ date = draw(dates(min_value=min_date, max_value=max_date, two_digit=two_digit))
1525
+ return date.year_month()
1432
1526
 
1527
+
1528
+ ##
1529
+
1530
+
1531
+ @composite
1532
+ def zone_infos(draw: DrawFn, /) -> ZoneInfo:
1533
+ """Strategy for generating time-zones."""
1534
+ time_zone = draw(timezones())
1535
+ if IS_LINUX: # skipif-not-linux
1536
+ _ = assume(time_zone.key not in _LINUX_DISALLOW_TIME_ZONES)
1537
+ with assume_does_not_raise(TimeZoneNotFoundError):
1538
+ _ = get_now(time_zone)
1539
+ return time_zone
1540
+
1541
+
1542
+ _LINUX_DISALLOW_TIME_ZONES: set[TimeZone | Literal["localtime"]] = {
1543
+ "Etc/UTC",
1544
+ "localtime",
1545
+ }
1546
+
1547
+ ##
1548
+
1549
+
1550
+ @composite
1551
+ def zoned_date_time_periods(
1552
+ draw: DrawFn,
1553
+ /,
1554
+ *,
1555
+ min_value: MaybeSearchStrategy[PlainDateTime | ZonedDateTime | None] = None,
1556
+ max_value: MaybeSearchStrategy[PlainDateTime | ZonedDateTime | None] = None,
1557
+ time_zone: MaybeSearchStrategy[TimeZoneLike] = UTC,
1558
+ ) -> ZonedDateTimePeriod:
1559
+ """Strategy for generating zoned date-time periods."""
1433
1560
  min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
1434
- time_zone_ = draw2(draw, time_zone)
1435
- if min_value_.tzinfo is None:
1436
- min_value_ = min_value_.replace(tzinfo=time_zone_)
1437
- else:
1438
- with assume_does_not_raise(OverflowError, match="date value out of range"):
1439
- min_value_ = min_value_.astimezone(time_zone_)
1440
- if max_value_.tzinfo is None:
1441
- max_value_ = max_value_.replace(tzinfo=time_zone_)
1442
- else:
1443
- with assume_does_not_raise(OverflowError, match="date value out of range"):
1444
- max_value_ = max_value_.astimezone(time_zone_)
1445
- try:
1446
- datetime = draw(
1447
- plain_datetimes(
1448
- min_value=min_value_.replace(tzinfo=None),
1449
- max_value=max_value_.replace(tzinfo=None),
1450
- round_=round_,
1451
- timedelta=timedelta,
1452
- rel_tol=rel_tol,
1453
- abs_tol=abs_tol,
1454
- )
1455
- )
1456
- except PlainDateTimesError as error:
1457
- raise ZonedDateTimesError(round_=error.round_) from None
1458
- datetime = datetime.replace(tzinfo=time_zone_)
1459
- _ = assume(min_value_ <= datetime <= max_value_)
1460
- if valid:
1461
- with assume_does_not_raise( # skipif-ci-and-windows
1462
- CheckValidZonedDateTimeError
1463
- ):
1464
- check_valid_zoned_datetime(datetime)
1465
- return datetime
1561
+ time_zone_: TimeZoneLike = draw2(draw, time_zone)
1562
+ strategy = zoned_date_times(
1563
+ min_value=min_value_, max_value=max_value_, time_zone=time_zone_
1564
+ )
1565
+ start, end = draw(pairs(strategy, sorted=True))
1566
+ return ZonedDateTimePeriod(start, end)
1466
1567
 
1467
1568
 
1468
- @dataclass(kw_only=True, slots=True)
1469
- class ZonedDateTimesError(Exception):
1470
- round_: RoundMode
1569
+ ##
1471
1570
 
1472
- @override
1473
- def __str__(self) -> str:
1474
- return "Rounding requires a timedelta; got None"
1475
1571
 
1572
+ @composite
1573
+ def zoned_date_times(
1574
+ draw: DrawFn,
1575
+ /,
1576
+ *,
1577
+ min_value: MaybeSearchStrategy[PlainDateTime | ZonedDateTime | None] = None,
1578
+ max_value: MaybeSearchStrategy[PlainDateTime | ZonedDateTime | None] = None,
1579
+ time_zone: MaybeSearchStrategy[TimeZoneLike] = UTC,
1580
+ ) -> ZonedDateTime:
1581
+ """Strategy for generating zoned date-times."""
1582
+ min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
1583
+ time_zone_ = to_zone_info(draw2(draw, time_zone))
1584
+ match min_value_:
1585
+ case None | PlainDateTime():
1586
+ ...
1587
+ case ZonedDateTime():
1588
+ with assume_does_not_raise(ValueError):
1589
+ min_value_ = min_value_.to_tz(time_zone_.key).to_plain()
1590
+ case never:
1591
+ assert_never(never)
1592
+ match max_value_:
1593
+ case None | PlainDateTime():
1594
+ ...
1595
+ case ZonedDateTime():
1596
+ with assume_does_not_raise(ValueError):
1597
+ max_value_ = max_value_.to_tz(time_zone_.key).to_plain()
1598
+ case never:
1599
+ assert_never(never)
1600
+ plain = draw(plain_date_times(min_value=min_value_, max_value=max_value_))
1601
+ with (
1602
+ assume_does_not_raise(RepeatedTime),
1603
+ assume_does_not_raise(SkippedTime),
1604
+ assume_does_not_raise(ValueError, match=r"Resulting time is out of range"),
1605
+ ):
1606
+ zoned = plain.assume_tz(time_zone_.key, disambiguate="raise")
1607
+ with assume_does_not_raise(OverflowError, match=r"date value out of range"):
1608
+ if not ((Date.MIN + DAY) <= zoned.date() <= (Date.MAX - DAY)):
1609
+ _ = zoned.py_datetime()
1610
+ return zoned
1611
+
1612
+
1613
+ zoned_date_times_2000 = zoned_date_times(
1614
+ min_value=ZonedDateTime(2000, 1, 1, tz=UTC.key),
1615
+ max_value=ZonedDateTime(2000, 12, 31, tz=UTC.key),
1616
+ )
1476
1617
 
1477
1618
  __all__ = [
1478
1619
  "Draw2Error",
1479
1620
  "MaybeSearchStrategy",
1480
- "PlainDateTimesError",
1481
1621
  "Shape",
1482
- "ZonedDateTimesError",
1483
1622
  "assume_does_not_raise",
1484
1623
  "bool_arrays",
1485
- "date_durations",
1486
- "dates_two_digit_year",
1487
- "datetime_durations",
1624
+ "date_deltas",
1625
+ "date_periods",
1626
+ "date_time_deltas",
1627
+ "dates",
1488
1628
  "draw2",
1489
1629
  "float32s",
1490
1630
  "float64s",
@@ -1492,27 +1632,27 @@ __all__ = [
1492
1632
  "floats_extra",
1493
1633
  "git_repos",
1494
1634
  "hashables",
1635
+ "import_froms",
1636
+ "imports",
1637
+ "int8s",
1638
+ "int16s",
1495
1639
  "int32s",
1496
1640
  "int64s",
1497
1641
  "int_arrays",
1498
1642
  "lists_fixed_length",
1499
- "min_and_max_datetimes",
1500
- "min_and_maybe_max_datetimes",
1501
- "min_and_maybe_max_sizes",
1502
- "min_and_maybe_max_sizes",
1503
- "months",
1504
- "namespace_mixins",
1643
+ "month_days",
1505
1644
  "numbers",
1506
1645
  "pairs",
1507
1646
  "paths",
1508
- "plain_datetimes",
1509
- "plain_datetimes",
1647
+ "permissions",
1648
+ "plain_date_times",
1649
+ "py_datetimes",
1650
+ "quadruples",
1510
1651
  "random_states",
1511
1652
  "sentinels",
1512
1653
  "sets_fixed_length",
1513
1654
  "setup_hypothesis_profiles",
1514
1655
  "slices",
1515
- "sqlalchemy_engines",
1516
1656
  "str_arrays",
1517
1657
  "temp_dirs",
1518
1658
  "temp_paths",
@@ -1522,10 +1662,19 @@ __all__ = [
1522
1662
  "text_clean",
1523
1663
  "text_digits",
1524
1664
  "text_printable",
1525
- "timedeltas_2w",
1665
+ "time_deltas",
1666
+ "time_periods",
1667
+ "times",
1526
1668
  "triples",
1669
+ "uint8s",
1670
+ "uint16s",
1527
1671
  "uint32s",
1528
1672
  "uint64s",
1673
+ "urls",
1529
1674
  "versions",
1530
- "zoned_datetimes",
1675
+ "year_months",
1676
+ "zone_infos",
1677
+ "zoned_date_time_periods",
1678
+ "zoned_date_times",
1679
+ "zoned_date_times_2000",
1531
1680
  ]