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/luigi.py DELETED
@@ -1,228 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from abc import ABC, abstractmethod
4
- from pathlib import Path
5
- from typing import TYPE_CHECKING, Any, Literal, cast, overload, override
6
-
7
- import luigi
8
- from luigi import Parameter, PathParameter, Target, Task
9
- from luigi import build as _build
10
-
11
- from utilities.datetime import EPOCH_UTC
12
-
13
- if TYPE_CHECKING:
14
- import datetime as dt
15
- from collections.abc import Iterable
16
-
17
- from luigi.execution_summary import LuigiRunResult
18
-
19
- from utilities.types import LogLevel, MaybeStr, PathLike
20
-
21
-
22
- # parameters
23
-
24
-
25
- class DateHourParameter(luigi.DateHourParameter):
26
- """A parameter which takes the value of an hourly `dt.datetime`."""
27
-
28
- def __init__(self, interval: int = 1, **kwargs: Any) -> None:
29
- super().__init__(interval, EPOCH_UTC, **kwargs)
30
-
31
- @override
32
- def normalize(self, dt: MaybeStr[dt.datetime]) -> dt.datetime:
33
- from utilities.whenever import ensure_zoned_datetime
34
-
35
- return ensure_zoned_datetime(dt)
36
-
37
- @override
38
- def parse(self, s: str) -> dt.datetime:
39
- from utilities.whenever import parse_zoned_datetime
40
-
41
- return parse_zoned_datetime(s)
42
-
43
- @override
44
- def serialize(self, dt: dt.datetime) -> str:
45
- from utilities.whenever import serialize_zoned_datetime
46
-
47
- return serialize_zoned_datetime(dt)
48
-
49
-
50
- class DateMinuteParameter(luigi.DateMinuteParameter):
51
- """A parameter which takes the value of a minutely `dt.datetime`."""
52
-
53
- def __init__(self, interval: int = 1, **kwargs: Any) -> None:
54
- super().__init__(interval=interval, start=EPOCH_UTC, **kwargs)
55
-
56
- @override
57
- def normalize(self, dt: MaybeStr[dt.datetime]) -> dt.datetime:
58
- from utilities.whenever import ensure_zoned_datetime
59
-
60
- return ensure_zoned_datetime(dt)
61
-
62
- @override
63
- def parse(self, s: str) -> dt.datetime:
64
- from utilities.whenever import parse_zoned_datetime
65
-
66
- return parse_zoned_datetime(s)
67
-
68
- @override
69
- def serialize(self, dt: dt.datetime) -> str:
70
- from utilities.whenever import serialize_zoned_datetime
71
-
72
- return serialize_zoned_datetime(dt)
73
-
74
-
75
- class DateSecondParameter(luigi.DateSecondParameter):
76
- """A parameter which takes the value of a secondly `dt.datetime`."""
77
-
78
- def __init__(self, interval: int = 1, **kwargs: Any) -> None:
79
- super().__init__(interval, EPOCH_UTC, **kwargs)
80
-
81
- @override
82
- def normalize(self, dt: MaybeStr[dt.datetime]) -> dt.datetime:
83
- from utilities.whenever import ensure_zoned_datetime
84
-
85
- return ensure_zoned_datetime(dt)
86
-
87
- @override
88
- def parse(self, s: str) -> dt.datetime:
89
- from utilities.whenever import parse_zoned_datetime
90
-
91
- return parse_zoned_datetime(s)
92
-
93
- @override
94
- def serialize(self, dt: dt.datetime) -> str:
95
- from utilities.whenever import serialize_zoned_datetime
96
-
97
- return serialize_zoned_datetime(dt)
98
-
99
-
100
- class TimeParameter(Parameter):
101
- """A parameter which takes the value of a `dt.time`."""
102
-
103
- @override
104
- def normalize(self, x: MaybeStr[dt.time]) -> dt.time:
105
- from utilities.whenever import ensure_time
106
-
107
- return ensure_time(x)
108
-
109
- @override
110
- def parse(self, x: str) -> dt.time:
111
- from utilities.whenever import parse_time
112
-
113
- return parse_time(x)
114
-
115
- @override
116
- def serialize(self, x: dt.time) -> str:
117
- from utilities.whenever import serialize_time
118
-
119
- return serialize_time(x)
120
-
121
-
122
- # targets
123
-
124
-
125
- class PathTarget(Target):
126
- """A local target whose `path` attribute is a Pathlib instance."""
127
-
128
- def __init__(self, path: PathLike, /) -> None:
129
- super().__init__()
130
- self.path = Path(path)
131
-
132
- @override
133
- def exists(self) -> bool: # pyright: ignore[reportIncompatibleMethodOverride]
134
- """Check if the target exists."""
135
- return self.path.exists()
136
-
137
-
138
- # tasks
139
-
140
-
141
- class ExternalTask(ABC, luigi.ExternalTask):
142
- """An external task with `exists()` defined here."""
143
-
144
- @abstractmethod
145
- def exists(self) -> bool:
146
- """Predicate on which the external task is deemed to exist."""
147
- msg = f"{self=}" # pragma: no cover
148
- raise NotImplementedError(msg) # pragma: no cover
149
-
150
- @override
151
- def output(self) -> _ExternalTaskDummyTarget: # pyright: ignore[reportIncompatibleMethodOverride]
152
- return _ExternalTaskDummyTarget(self)
153
-
154
-
155
- class _ExternalTaskDummyTarget(Target):
156
- """Dummy target for `ExternalTask`."""
157
-
158
- def __init__(self, task: ExternalTask, /) -> None:
159
- super().__init__()
160
- self._task = task
161
-
162
- @override
163
- def exists(self) -> bool: # pyright: ignore[reportIncompatibleMethodOverride]
164
- return self._task.exists()
165
-
166
-
167
- class ExternalFile(ExternalTask):
168
- """Await an external file on the local disk."""
169
-
170
- path: Path = cast("Any", PathParameter())
171
-
172
- @override
173
- def exists(self) -> bool:
174
- return self.path.exists()
175
-
176
-
177
- # functions
178
-
179
-
180
- @overload
181
- def build(
182
- task: Iterable[Task],
183
- /,
184
- *,
185
- detailed_summary: Literal[False] = False,
186
- local_scheduler: bool = False,
187
- log_level: LogLevel | None = None,
188
- workers: int | None = None,
189
- ) -> bool: ...
190
- @overload
191
- def build(
192
- task: Iterable[Task],
193
- /,
194
- *,
195
- detailed_summary: Literal[True],
196
- local_scheduler: bool = False,
197
- log_level: LogLevel | None = None,
198
- workers: int | None = None,
199
- ) -> LuigiRunResult: ...
200
- def build(
201
- task: Iterable[Task],
202
- /,
203
- *,
204
- detailed_summary: bool = False,
205
- local_scheduler: bool = False,
206
- log_level: LogLevel | None = None,
207
- workers: int | None = None,
208
- ) -> bool | LuigiRunResult:
209
- """Build a set of tasks."""
210
- return _build(
211
- task,
212
- detailed_summary=detailed_summary,
213
- local_scheduler=local_scheduler,
214
- **({} if log_level is None else {"log_level": log_level}),
215
- **({} if workers is None else {"workers": workers}),
216
- )
217
-
218
-
219
- __all__ = [
220
- "DateHourParameter",
221
- "DateMinuteParameter",
222
- "DateSecondParameter",
223
- "ExternalFile",
224
- "ExternalTask",
225
- "PathTarget",
226
- "TimeParameter",
227
- "build",
228
- ]
utilities/period.py DELETED
@@ -1,324 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import datetime as dt
4
- from dataclasses import dataclass, field
5
- from functools import cached_property
6
- from itertools import permutations
7
- from typing import (
8
- TYPE_CHECKING,
9
- Generic,
10
- Literal,
11
- Self,
12
- TypedDict,
13
- TypeVar,
14
- assert_never,
15
- cast,
16
- override,
17
- )
18
-
19
- from utilities.datetime import ZERO_TIME
20
- from utilities.functions import get_class_name
21
- from utilities.iterables import OneUniqueNonUniqueError, always_iterable, one_unique
22
- from utilities.sentinel import Sentinel, sentinel
23
- from utilities.typing import is_instance_gen
24
- from utilities.whenever import (
25
- serialize_date,
26
- serialize_plain_datetime,
27
- serialize_zoned_datetime,
28
- )
29
- from utilities.zoneinfo import EnsureTimeZoneError, ensure_time_zone
30
-
31
- if TYPE_CHECKING:
32
- from zoneinfo import ZoneInfo
33
-
34
- from utilities.iterables import MaybeIterable
35
- from utilities.types import DateOrDateTime
36
-
37
-
38
- type _DateOrDateTime = Literal["date", "datetime"]
39
- _TPeriod = TypeVar("_TPeriod", dt.date, dt.datetime)
40
-
41
-
42
- class _PeriodAsDict(TypedDict, Generic[_TPeriod]):
43
- start: _TPeriod
44
- end: _TPeriod
45
-
46
-
47
- @dataclass(repr=False, order=True, unsafe_hash=True)
48
- class Period(Generic[_TPeriod]):
49
- """A period of time."""
50
-
51
- start: _TPeriod
52
- end: _TPeriod
53
- req_duration: MaybeIterable[dt.timedelta] | None = field(
54
- default=None, repr=False, kw_only=True
55
- )
56
- min_duration: dt.timedelta | None = field(default=None, repr=False, kw_only=True)
57
- max_duration: dt.timedelta | None = field(default=None, repr=False, kw_only=True)
58
-
59
- def __post_init__(self) -> None:
60
- if any(
61
- is_instance_gen(left, cls) is not is_instance_gen(right, cls)
62
- for left, right in permutations([self.start, self.end], 2)
63
- for cls in [dt.date, dt.datetime]
64
- ):
65
- raise _PeriodDateAndDateTimeMixedError(start=self.start, end=self.end)
66
- for date in [self.start, self.end]:
67
- if isinstance(date, dt.datetime):
68
- try:
69
- _ = ensure_time_zone(date)
70
- except EnsureTimeZoneError:
71
- raise _PeriodNaiveDateTimeError(
72
- start=self.start, end=self.end
73
- ) from None
74
- duration = self.end - self.start
75
- if duration < ZERO_TIME:
76
- raise _PeriodInvalidError(start=self.start, end=self.end)
77
- if (self.req_duration is not None) and (
78
- duration not in always_iterable(self.req_duration)
79
- ):
80
- raise _PeriodReqDurationError(
81
- start=self.start,
82
- end=self.end,
83
- duration=duration,
84
- req_duration=self.req_duration,
85
- )
86
- if (self.min_duration is not None) and (duration < self.min_duration):
87
- raise _PeriodMinDurationError(
88
- start=self.start,
89
- end=self.end,
90
- duration=duration,
91
- min_duration=self.min_duration,
92
- )
93
- if (self.max_duration is not None) and (duration > self.max_duration):
94
- raise _PeriodMaxDurationError(
95
- start=self.start,
96
- end=self.end,
97
- duration=duration,
98
- max_duration=self.max_duration,
99
- )
100
-
101
- def __add__(self, other: dt.timedelta, /) -> Self:
102
- """Offset the period."""
103
- return self.replace(start=self.start + other, end=self.end + other)
104
-
105
- def __contains__(self, other: DateOrDateTime, /) -> bool:
106
- """Check if a date/datetime lies in the period."""
107
- match self.kind:
108
- case "date":
109
- if isinstance(other, dt.datetime):
110
- raise _PeriodDateContainsDateTimeError(
111
- start=self.start, end=self.end
112
- )
113
- case "datetime":
114
- if not isinstance(other, dt.datetime):
115
- raise _PeriodDateTimeContainsDateError(
116
- start=self.start, end=self.end
117
- )
118
- case _ as never:
119
- assert_never(never)
120
- return self.start <= other <= self.end
121
-
122
- @override
123
- def __repr__(self) -> str:
124
- cls = get_class_name(self)
125
- match self.kind:
126
- case "date":
127
- result = cast("Period[dt.date]", self)
128
- start, end = map(serialize_date, [result.start, result.end])
129
- return f"{cls}({start}, {end})"
130
- case "datetime":
131
- result = cast("Period[dt.datetime]", self)
132
- try:
133
- time_zone = result.time_zone
134
- except _PeriodTimeZoneNonUniqueError:
135
- start, end = map(
136
- serialize_zoned_datetime, [result.start, result.end]
137
- )
138
- return f"{cls}({start}, {end})"
139
- start, end = (
140
- serialize_plain_datetime(t.replace(tzinfo=None))
141
- for t in [result.start, result.end]
142
- )
143
- return f"{cls}({start}, {end}, {time_zone})"
144
- case _ as never:
145
- assert_never(never)
146
-
147
- def __sub__(self, other: dt.timedelta, /) -> Self:
148
- """Offset the period."""
149
- return self.replace(start=self.start - other, end=self.end - other)
150
-
151
- def astimezone(self, time_zone: ZoneInfo, /) -> Self:
152
- """Convert the timezone of the period, if it is a datetime period."""
153
- match self.kind:
154
- case "date":
155
- raise _PeriodAsTimeZoneInapplicableError(start=self.start, end=self.end)
156
- case "datetime":
157
- result = cast("Period[dt.datetime]", self)
158
- result = result.replace(
159
- start=result.start.astimezone(time_zone),
160
- end=result.end.astimezone(time_zone),
161
- )
162
- return cast("Self", result)
163
- case _ as never:
164
- assert_never(never)
165
-
166
- @cached_property
167
- def duration(self) -> dt.timedelta:
168
- """The duration of the period."""
169
- return self.end - self.start
170
-
171
- @cached_property
172
- def kind(self) -> _DateOrDateTime:
173
- """The kind of the period."""
174
- return "date" if is_instance_gen(self.start, dt.date) else "datetime"
175
-
176
- def replace(
177
- self,
178
- *,
179
- start: _TPeriod | None = None,
180
- end: _TPeriod | None = None,
181
- req_duration: MaybeIterable[dt.timedelta] | None | Sentinel = sentinel,
182
- min_duration: dt.timedelta | None | Sentinel = sentinel,
183
- max_duration: dt.timedelta | None | Sentinel = sentinel,
184
- ) -> Self:
185
- """Replace elements of the period."""
186
- return type(self)(
187
- self.start if start is None else start,
188
- self.end if end is None else end,
189
- req_duration=self.req_duration
190
- if isinstance(req_duration, Sentinel)
191
- else req_duration,
192
- min_duration=self.min_duration
193
- if isinstance(min_duration, Sentinel)
194
- else min_duration,
195
- max_duration=self.max_duration
196
- if isinstance(max_duration, Sentinel)
197
- else max_duration,
198
- )
199
-
200
- @cached_property
201
- def time_zone(self) -> ZoneInfo:
202
- """The time zone of the period."""
203
- match self.kind:
204
- case "date":
205
- raise _PeriodTimeZoneInapplicableError(
206
- start=self.start, end=self.end
207
- ) from None
208
- case "datetime":
209
- result = cast("Period[dt.datetime]", self)
210
- try:
211
- return one_unique(map(ensure_time_zone, [result.start, result.end]))
212
- except OneUniqueNonUniqueError as error:
213
- raise _PeriodTimeZoneNonUniqueError(
214
- start=self.start,
215
- end=self.end,
216
- first=error.first,
217
- second=error.second,
218
- ) from None
219
- case _ as never:
220
- assert_never(never)
221
-
222
- def to_dict(self) -> _PeriodAsDict:
223
- """Convert the period to a dictionary."""
224
- return {"start": self.start, "end": self.end}
225
-
226
-
227
- @dataclass(kw_only=True, slots=True)
228
- class PeriodError(Generic[_TPeriod], Exception):
229
- start: _TPeriod
230
- end: _TPeriod
231
-
232
-
233
- @dataclass(kw_only=True, slots=True)
234
- class _PeriodDateAndDateTimeMixedError(PeriodError[_TPeriod]):
235
- @override
236
- def __str__(self) -> str:
237
- return f"Invalid period; got date and datetime mix ({self.start}, {self.end})"
238
-
239
-
240
- @dataclass(kw_only=True, slots=True)
241
- class _PeriodNaiveDateTimeError(PeriodError[_TPeriod]):
242
- @override
243
- def __str__(self) -> str:
244
- return f"Invalid period; got naive datetime(s) ({self.start}, {self.end})"
245
-
246
-
247
- @dataclass(kw_only=True, slots=True)
248
- class _PeriodInvalidError(PeriodError[_TPeriod]):
249
- @override
250
- def __str__(self) -> str:
251
- return f"Invalid period; got {self.start} > {self.end}"
252
-
253
-
254
- @dataclass(kw_only=True, slots=True)
255
- class _PeriodReqDurationError(PeriodError[_TPeriod]):
256
- duration: dt.timedelta
257
- req_duration: MaybeIterable[dt.timedelta]
258
-
259
- @override
260
- def __str__(self) -> str:
261
- return f"Period must have duration {self.req_duration}; got {self.duration})"
262
-
263
-
264
- @dataclass(kw_only=True, slots=True)
265
- class _PeriodMinDurationError(PeriodError[_TPeriod]):
266
- duration: dt.timedelta
267
- min_duration: dt.timedelta
268
-
269
- @override
270
- def __str__(self) -> str:
271
- return (
272
- f"Period must have min duration {self.min_duration}; got {self.duration})"
273
- )
274
-
275
-
276
- @dataclass(kw_only=True, slots=True)
277
- class _PeriodMaxDurationError(PeriodError[_TPeriod]):
278
- duration: dt.timedelta
279
- max_duration: dt.timedelta
280
-
281
- @override
282
- def __str__(self) -> str:
283
- return f"Period must have duration at most {self.max_duration}; got {self.duration})"
284
-
285
-
286
- @dataclass(kw_only=True, slots=True)
287
- class _PeriodAsTimeZoneInapplicableError(PeriodError[_TPeriod]):
288
- @override
289
- def __str__(self) -> str:
290
- return "Period of dates does not have a timezone attribute"
291
-
292
-
293
- @dataclass(kw_only=True, slots=True)
294
- class _PeriodDateContainsDateTimeError(PeriodError[_TPeriod]):
295
- @override
296
- def __str__(self) -> str:
297
- return "Period of dates cannot contain datetimes"
298
-
299
-
300
- @dataclass(kw_only=True, slots=True)
301
- class _PeriodDateTimeContainsDateError(PeriodError[_TPeriod]):
302
- @override
303
- def __str__(self) -> str:
304
- return "Period of datetimes cannot contain dates"
305
-
306
-
307
- @dataclass(kw_only=True, slots=True)
308
- class _PeriodTimeZoneInapplicableError(PeriodError[_TPeriod]):
309
- @override
310
- def __str__(self) -> str:
311
- return "Period of dates does not have a timezone attribute"
312
-
313
-
314
- @dataclass(kw_only=True, slots=True)
315
- class _PeriodTimeZoneNonUniqueError(PeriodError[_TPeriod]):
316
- first: ZoneInfo
317
- second: ZoneInfo
318
-
319
- @override
320
- def __str__(self) -> str:
321
- return f"Period must contain exactly one time zone; got {self.first} and {self.second}"
322
-
323
-
324
- __all__ = ["Period", "PeriodError"]
utilities/pyrsistent.py DELETED
@@ -1,89 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import TYPE_CHECKING, Any, TypeVar, dataclass_transform, overload
4
-
5
- from pyrsistent import PRecord as _PRecord
6
- from pyrsistent import field as _field
7
- from pyrsistent._field_common import (
8
- PFIELD_NO_FACTORY,
9
- PFIELD_NO_INITIAL,
10
- PFIELD_NO_INVARIANT,
11
- PFIELD_NO_SERIALIZER,
12
- PFIELD_NO_TYPE,
13
- )
14
-
15
- if TYPE_CHECKING:
16
- from collections.abc import Callable
17
-
18
- from utilities.types import TypeLike
19
-
20
-
21
- _T = TypeVar("_T")
22
- _U = TypeVar("_U")
23
-
24
-
25
- @overload
26
- def field(
27
- *,
28
- type: type[_T],
29
- invariant: Callable[[Any], tuple[bool, Any]] = ...,
30
- default: Any = ...,
31
- mandatory: bool = ...,
32
- factory: Callable[[_U], _U] = ...,
33
- serializer: Callable[[Any, Any], Any] = ...,
34
- ) -> _T: ...
35
- @overload
36
- def field(
37
- *,
38
- type: tuple[type[_T]],
39
- invariant: Callable[[Any], tuple[bool, Any]] = ...,
40
- default: Any = ...,
41
- mandatory: bool = ...,
42
- factory: Callable[[_U], _U] = ...,
43
- serializer: Callable[[Any, Any], Any] = ...,
44
- ) -> _T: ...
45
- @overload
46
- def field(
47
- *,
48
- type: tuple[type[_T], type[_U]],
49
- invariant: Callable[[Any], tuple[bool, Any]] = ...,
50
- default: Any = ...,
51
- mandatory: bool = ...,
52
- factory: Callable[[_U], _U] = ...,
53
- serializer: Callable[[Any, Any], Any] = ...,
54
- ) -> _T | _U: ...
55
- @overload
56
- def field(
57
- *,
58
- type: tuple[Any, ...] = ...,
59
- invariant: Callable[[Any], tuple[bool, Any]] = ...,
60
- default: Any = ...,
61
- mandatory: bool = ...,
62
- factory: Callable[[_U], _U] = ...,
63
- serializer: Callable[[Any, Any], Any] = ...,
64
- ) -> Any: ...
65
- def field(
66
- *,
67
- type: TypeLike[_T] = PFIELD_NO_TYPE, # noqa: A002
68
- invariant: Callable[[Any], tuple[bool, Any]] = PFIELD_NO_INVARIANT,
69
- default: Any = PFIELD_NO_INITIAL,
70
- mandatory: bool = False,
71
- factory: Callable[[_U], _U] = PFIELD_NO_FACTORY,
72
- serializer: Callable[[Any, Any], Any] = PFIELD_NO_SERIALIZER,
73
- ) -> Any:
74
- """Field specification factory for :py:class:`PRecord`."""
75
- return _field(
76
- type=type,
77
- invariant=invariant,
78
- initial=default,
79
- mandatory=mandatory,
80
- factory=factory,
81
- serializer=serializer,
82
- )
83
-
84
-
85
- @dataclass_transform(kw_only_default=True, field_specifiers=(field,))
86
- class PRecord(_PRecord): ...
87
-
88
-
89
- __all__ = ["PRecord", "field"]