dycw-utilities 0.148.5__py3-none-any.whl → 0.175.31__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.

Potentially problematic release.


This version of dycw-utilities might be problematic. Click here for more details.

Files changed (84) hide show
  1. dycw_utilities-0.175.31.dist-info/METADATA +34 -0
  2. dycw_utilities-0.175.31.dist-info/RECORD +103 -0
  3. dycw_utilities-0.175.31.dist-info/WHEEL +4 -0
  4. {dycw_utilities-0.148.5.dist-info → dycw_utilities-0.175.31.dist-info}/entry_points.txt +1 -0
  5. utilities/__init__.py +1 -1
  6. utilities/altair.py +10 -7
  7. utilities/asyncio.py +113 -64
  8. utilities/atomicwrites.py +1 -1
  9. utilities/atools.py +64 -4
  10. utilities/cachetools.py +9 -6
  11. utilities/click.py +144 -49
  12. utilities/concurrent.py +1 -1
  13. utilities/contextlib.py +4 -2
  14. utilities/contextvars.py +20 -1
  15. utilities/cryptography.py +3 -3
  16. utilities/dataclasses.py +15 -28
  17. utilities/docker.py +381 -0
  18. utilities/enum.py +2 -2
  19. utilities/errors.py +1 -1
  20. utilities/fastapi.py +8 -3
  21. utilities/fpdf2.py +2 -2
  22. utilities/functions.py +20 -297
  23. utilities/git.py +19 -0
  24. utilities/grp.py +28 -0
  25. utilities/hypothesis.py +361 -79
  26. utilities/importlib.py +17 -1
  27. utilities/inflect.py +1 -1
  28. utilities/iterables.py +12 -58
  29. utilities/jinja2.py +148 -0
  30. utilities/json.py +1 -1
  31. utilities/libcst.py +7 -7
  32. utilities/logging.py +74 -85
  33. utilities/math.py +8 -4
  34. utilities/more_itertools.py +4 -6
  35. utilities/operator.py +1 -1
  36. utilities/orjson.py +86 -34
  37. utilities/os.py +49 -2
  38. utilities/parse.py +2 -2
  39. utilities/pathlib.py +66 -34
  40. utilities/permissions.py +298 -0
  41. utilities/platform.py +4 -4
  42. utilities/polars.py +934 -420
  43. utilities/polars_ols.py +1 -1
  44. utilities/postgres.py +296 -174
  45. utilities/pottery.py +8 -73
  46. utilities/pqdm.py +3 -3
  47. utilities/pwd.py +28 -0
  48. utilities/pydantic.py +11 -0
  49. utilities/pydantic_settings.py +240 -0
  50. utilities/pydantic_settings_sops.py +76 -0
  51. utilities/pyinstrument.py +5 -5
  52. utilities/pytest.py +155 -46
  53. utilities/pytest_plugins/pytest_randomly.py +1 -1
  54. utilities/pytest_plugins/pytest_regressions.py +7 -3
  55. utilities/pytest_regressions.py +27 -8
  56. utilities/random.py +11 -6
  57. utilities/re.py +1 -1
  58. utilities/redis.py +101 -64
  59. utilities/sentinel.py +10 -0
  60. utilities/shelve.py +4 -1
  61. utilities/shutil.py +25 -0
  62. utilities/slack_sdk.py +8 -3
  63. utilities/sqlalchemy.py +422 -352
  64. utilities/sqlalchemy_polars.py +28 -52
  65. utilities/string.py +1 -1
  66. utilities/subprocess.py +1947 -0
  67. utilities/tempfile.py +95 -4
  68. utilities/testbook.py +50 -0
  69. utilities/text.py +165 -42
  70. utilities/timer.py +2 -2
  71. utilities/traceback.py +46 -36
  72. utilities/types.py +62 -23
  73. utilities/typing.py +479 -19
  74. utilities/uuid.py +42 -5
  75. utilities/version.py +27 -26
  76. utilities/whenever.py +661 -151
  77. utilities/zoneinfo.py +80 -22
  78. dycw_utilities-0.148.5.dist-info/METADATA +0 -41
  79. dycw_utilities-0.148.5.dist-info/RECORD +0 -95
  80. dycw_utilities-0.148.5.dist-info/WHEEL +0 -4
  81. dycw_utilities-0.148.5.dist-info/licenses/LICENSE +0 -21
  82. utilities/eventkit.py +0 -388
  83. utilities/period.py +0 -237
  84. utilities/typed_settings.py +0 -144
utilities/click.py CHANGED
@@ -3,21 +3,22 @@ from __future__ import annotations
3
3
  import enum
4
4
  import ipaddress
5
5
  import pathlib
6
+ import uuid
7
+ from enum import StrEnum
6
8
  from typing import TYPE_CHECKING, TypedDict, assert_never, override
7
9
 
8
- import click
9
10
  import whenever
10
11
  from click import Choice, Context, Parameter, ParamType
11
12
  from click.types import IntParamType, StringParamType
12
13
 
13
14
  from utilities.enum import EnsureEnumError, ensure_enum
14
- from utilities.functions import EnsureStrError, ensure_str, get_class_name
15
- from utilities.iterables import is_iterable_not_str
15
+ from utilities.functions import EnsureStrError, ensure_str, get_class, get_class_name
16
+ from utilities.iterables import is_iterable_not_str, one_unique
16
17
  from utilities.parse import ParseObjectError, parse_object
17
18
  from utilities.text import split_str
18
19
 
19
20
  if TYPE_CHECKING:
20
- from collections.abc import Iterable, Sequence
21
+ from collections.abc import Iterable
21
22
 
22
23
  from utilities.types import (
23
24
  DateDeltaLike,
@@ -28,6 +29,7 @@ if TYPE_CHECKING:
28
29
  IPv6AddressLike,
29
30
  MaybeStr,
30
31
  MonthDayLike,
32
+ PathLike,
31
33
  PlainDateTimeLike,
32
34
  TimeDeltaLike,
33
35
  TimeLike,
@@ -36,27 +38,23 @@ if TYPE_CHECKING:
36
38
  )
37
39
 
38
40
 
39
- FilePath = click.Path(file_okay=True, dir_okay=False, path_type=pathlib.Path)
40
- DirPath = click.Path(file_okay=False, dir_okay=True, path_type=pathlib.Path)
41
- ExistingFilePath = click.Path(
42
- exists=True, file_okay=True, dir_okay=False, path_type=pathlib.Path
43
- )
44
- ExistingDirPath = click.Path(
45
- exists=True, file_okay=False, dir_okay=True, path_type=pathlib.Path
46
- )
47
-
41
+ class _ContextSettings(TypedDict):
42
+ context_settings: _ContextSettingsInner
48
43
 
49
- class _HelpOptionNames(TypedDict):
50
- help_option_names: Sequence[str]
51
44
 
45
+ class _ContextSettingsInner(TypedDict):
46
+ max_content_width: int
47
+ help_option_names: list[str]
48
+ show_default: bool
52
49
 
53
- class _ContextSettings(TypedDict):
54
- context_settings: _HelpOptionNames
55
50
 
56
-
57
- CONTEXT_SETTINGS_HELP_OPTION_NAMES = _ContextSettings(
58
- context_settings=_HelpOptionNames(help_option_names=["-h", "--help"])
51
+ _MAX_CONTENT_WIDTH = 120
52
+ _CONTEXT_SETTINGS_INNER = _ContextSettingsInner(
53
+ max_content_width=_MAX_CONTENT_WIDTH,
54
+ help_option_names=["-h", "--help"],
55
+ show_default=True,
59
56
  )
57
+ CONTEXT_SETTINGS = _ContextSettings(context_settings=_CONTEXT_SETTINGS_INNER)
60
58
 
61
59
 
62
60
  # parameters
@@ -81,10 +79,10 @@ class Date(ParamType):
81
79
  return value
82
80
  case str():
83
81
  try:
84
- return whenever.Date.parse_common_iso(value)
82
+ return whenever.Date.parse_iso(value)
85
83
  except ValueError as error:
86
84
  self.fail(str(error), param, ctx)
87
- case _ as never:
85
+ case never:
88
86
  assert_never(never)
89
87
 
90
88
 
@@ -107,10 +105,10 @@ class DateDelta(ParamType):
107
105
  return value
108
106
  case str():
109
107
  try:
110
- return whenever.DateDelta.parse_common_iso(value)
108
+ return whenever.DateDelta.parse_iso(value)
111
109
  except ValueError as error:
112
110
  self.fail(str(error), param, ctx)
113
- case _ as never:
111
+ case never:
114
112
  assert_never(never)
115
113
 
116
114
 
@@ -133,10 +131,10 @@ class DateTimeDelta(ParamType):
133
131
  return value
134
132
  case str():
135
133
  try:
136
- return whenever.DateTimeDelta.parse_common_iso(value)
134
+ return whenever.DateTimeDelta.parse_iso(value)
137
135
  except ValueError as error:
138
136
  self.fail(str(error), param, ctx)
139
- case _ as never:
137
+ case never:
140
138
  assert_never(never)
141
139
 
142
140
 
@@ -144,10 +142,13 @@ class Enum[E: enum.Enum](ParamType):
144
142
  """An enum-valued parameter."""
145
143
 
146
144
  @override
147
- def __init__(self, enum: type[E], /, *, case_sensitive: bool = False) -> None:
145
+ def __init__(
146
+ self, enum: type[E], /, *, value: bool = False, case_sensitive: bool = False
147
+ ) -> None:
148
148
  cls = get_class_name(enum)
149
149
  self.name = f"enum[{cls}]"
150
150
  self._enum = enum
151
+ self._value = issubclass(self._enum, StrEnum) or value
151
152
  self._case_sensitive = case_sensitive
152
153
  super().__init__()
153
154
 
@@ -169,7 +170,52 @@ class Enum[E: enum.Enum](ParamType):
169
170
  @override
170
171
  def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
171
172
  _ = ctx
172
- desc = ",".join(e.name for e in self._enum)
173
+ desc = ",".join(str(e.value) if self._value else e.name for e in self._enum)
174
+ return _make_metavar(param, desc)
175
+
176
+
177
+ class EnumPartial[E: enum.Enum](ParamType):
178
+ """An enum-valued parameter."""
179
+
180
+ @override
181
+ def __init__(
182
+ self,
183
+ members: Iterable[E],
184
+ /,
185
+ *,
186
+ value: bool = False,
187
+ case_sensitive: bool = False,
188
+ ) -> None:
189
+ self._members = list(members)
190
+ self._enum = one_unique(get_class(e) for e in self._members)
191
+ cls = get_class_name(self._enum)
192
+ self.name = f"enum-partial[{cls}]"
193
+ self._value = issubclass(self._enum, StrEnum) or value
194
+ self._case_sensitive = case_sensitive
195
+ super().__init__()
196
+
197
+ @override
198
+ def __repr__(self) -> str:
199
+ cls = get_class_name(self._enum)
200
+ return f"ENUMPARTIAL[{cls}]"
201
+
202
+ @override
203
+ def convert(
204
+ self, value: EnumLike[E], param: Parameter | None, ctx: Context | None
205
+ ) -> E:
206
+ """Convert a value into the `Enum` type."""
207
+ try:
208
+ enum = ensure_enum(value, self._enum, case_sensitive=self._case_sensitive)
209
+ except EnsureEnumError as error:
210
+ self.fail(str(error), param, ctx)
211
+ if enum in self._members:
212
+ return enum
213
+ return self.fail(f"{enum.value!r} is not a selected member")
214
+
215
+ @override
216
+ def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
217
+ _ = ctx
218
+ desc = ",".join(str(e.value) if self._value else e.name for e in self._members)
173
219
  return _make_metavar(param, desc)
174
220
 
175
221
 
@@ -195,7 +241,7 @@ class IPv4Address(ParamType):
195
241
  return parse_object(ipaddress.IPv4Address, value)
196
242
  except ParseObjectError as error:
197
243
  self.fail(str(error), param, ctx)
198
- case _ as never:
244
+ case never:
199
245
  assert_never(never)
200
246
 
201
247
 
@@ -221,7 +267,7 @@ class IPv6Address(ParamType):
221
267
  return parse_object(ipaddress.IPv6Address, value)
222
268
  except ParseObjectError as error:
223
269
  self.fail(str(error), param, ctx)
224
- case _ as never:
270
+ case never:
225
271
  assert_never(never)
226
272
 
227
273
 
@@ -244,10 +290,33 @@ class MonthDay(ParamType):
244
290
  return value
245
291
  case str():
246
292
  try:
247
- return whenever.MonthDay.parse_common_iso(value)
293
+ return whenever.MonthDay.parse_iso(value)
248
294
  except ValueError as error:
249
295
  self.fail(str(error), param, ctx)
250
- case _ as never:
296
+ case never:
297
+ assert_never(never)
298
+
299
+
300
+ class Path(ParamType):
301
+ """A path-valued parameter."""
302
+
303
+ name = "path"
304
+
305
+ @override
306
+ def __repr__(self) -> str:
307
+ return self.name.upper()
308
+
309
+ @override
310
+ def convert(
311
+ self, value: PathLike, param: Parameter | None, ctx: Context | None
312
+ ) -> pathlib.Path:
313
+ """Convert a value into the `Path` type."""
314
+ match value:
315
+ case pathlib.Path():
316
+ return value.expanduser()
317
+ case str():
318
+ return pathlib.Path(value).expanduser()
319
+ case never:
251
320
  assert_never(never)
252
321
 
253
322
 
@@ -270,10 +339,10 @@ class PlainDateTime(ParamType):
270
339
  return value
271
340
  case str():
272
341
  try:
273
- return whenever.PlainDateTime.parse_common_iso(value)
342
+ return whenever.PlainDateTime.parse_iso(value)
274
343
  except ValueError as error:
275
344
  self.fail(str(error), param, ctx)
276
- case _ as never:
345
+ case never:
277
346
  assert_never(never)
278
347
 
279
348
 
@@ -296,10 +365,10 @@ class Time(ParamType):
296
365
  return value
297
366
  case str():
298
367
  try:
299
- return whenever.Time.parse_common_iso(value)
368
+ return whenever.Time.parse_iso(value)
300
369
  except ValueError as error:
301
370
  self.fail(str(error), param, ctx)
302
- case _ as never:
371
+ case never:
303
372
  assert_never(never)
304
373
 
305
374
 
@@ -322,10 +391,36 @@ class TimeDelta(ParamType):
322
391
  return value
323
392
  case str():
324
393
  try:
325
- return whenever.TimeDelta.parse_common_iso(value)
394
+ return whenever.TimeDelta.parse_iso(value)
395
+ except ValueError as error:
396
+ self.fail(str(error), param, ctx)
397
+ case never:
398
+ assert_never(never)
399
+
400
+
401
+ class UUID(ParamType):
402
+ """A UUID-valued parameter."""
403
+
404
+ name = "uuid"
405
+
406
+ @override
407
+ def __repr__(self) -> str:
408
+ return self.name.upper()
409
+
410
+ @override
411
+ def convert(
412
+ self, value: uuid.UUID | str, param: Parameter | None, ctx: Context | None
413
+ ) -> uuid.UUID:
414
+ """Convert a value into the `UUID` type."""
415
+ match value:
416
+ case uuid.UUID():
417
+ return value
418
+ case str():
419
+ try:
420
+ return uuid.UUID(value)
326
421
  except ValueError as error:
327
422
  self.fail(str(error), param, ctx)
328
- case _ as never:
423
+ case never:
329
424
  assert_never(never)
330
425
 
331
426
 
@@ -348,10 +443,10 @@ class YearMonth(ParamType):
348
443
  return value
349
444
  case str():
350
445
  try:
351
- return whenever.YearMonth.parse_common_iso(value)
446
+ return whenever.YearMonth.parse_iso(value)
352
447
  except ValueError as error:
353
448
  self.fail(str(error), param, ctx)
354
- case _ as never:
449
+ case never:
355
450
  assert_never(never)
356
451
 
357
452
 
@@ -374,10 +469,10 @@ class ZonedDateTime(ParamType):
374
469
  return value
375
470
  case str():
376
471
  try:
377
- return whenever.ZonedDateTime.parse_common_iso(value)
472
+ return whenever.ZonedDateTime.parse_iso(value)
378
473
  except ValueError as error:
379
474
  self.fail(str(error), param, ctx)
380
- case _ as never:
475
+ case never:
381
476
  assert_never(never)
382
477
 
383
478
 
@@ -429,7 +524,7 @@ class FrozenSetChoices(FrozenSetParameter[Choice, str]):
429
524
  @override
430
525
  def __init__(
431
526
  self,
432
- choices: Sequence[str],
527
+ choices: list[str],
433
528
  /,
434
529
  *,
435
530
  case_sensitive: bool = False,
@@ -514,7 +609,7 @@ class ListChoices(ListParameter[Choice, str]):
514
609
  @override
515
610
  def __init__(
516
611
  self,
517
- choices: Sequence[str],
612
+ choices: list[str],
518
613
  /,
519
614
  *,
520
615
  case_sensitive: bool = False,
@@ -560,15 +655,13 @@ def _make_metavar(param: Parameter, desc: str, /) -> str:
560
655
 
561
656
 
562
657
  __all__ = [
563
- "CONTEXT_SETTINGS_HELP_OPTION_NAMES",
658
+ "CONTEXT_SETTINGS",
659
+ "UUID",
564
660
  "Date",
565
661
  "DateDelta",
566
662
  "DateTimeDelta",
567
- "DirPath",
568
663
  "Enum",
569
- "ExistingDirPath",
570
- "ExistingFilePath",
571
- "FilePath",
664
+ "EnumPartial",
572
665
  "FrozenSetChoices",
573
666
  "FrozenSetEnums",
574
667
  "FrozenSetParameter",
@@ -581,6 +674,8 @@ __all__ = [
581
674
  "ListParameter",
582
675
  "ListStrs",
583
676
  "MonthDay",
677
+ "Path",
678
+ "Path",
584
679
  "PlainDateTime",
585
680
  "Time",
586
681
  "TimeDelta",
utilities/concurrent.py CHANGED
@@ -84,7 +84,7 @@ def concurrent_starmap[T](
84
84
  initargs=initargs,
85
85
  ) as pool:
86
86
  result = pool.map(apply, iterable, timeout=timeout, chunksize=chunksize)
87
- case _ as never:
87
+ case never:
88
88
  assert_never(never)
89
89
  return list(result)
90
90
 
utilities/contextlib.py CHANGED
@@ -8,7 +8,7 @@ from contextlib import (
8
8
  asynccontextmanager,
9
9
  contextmanager,
10
10
  )
11
- from functools import partial
11
+ from functools import partial, wraps
12
12
  from signal import SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM, getsignal, signal
13
13
  from typing import TYPE_CHECKING, Any, assert_never, cast, overload
14
14
 
@@ -77,6 +77,7 @@ def enhanced_context_manager[**P, T_co](
77
77
  make_gcm = contextmanager(func)
78
78
 
79
79
  @contextmanager
80
+ @wraps(func)
80
81
  def wrapped(*args: P.args, **kwargs: P.kwargs) -> Iterator[T_co]:
81
82
  gcm = make_gcm(*args, **kwargs)
82
83
  sigabrt0 = _swap_handler(SIGABRT, gcm) if sigabrt else None
@@ -159,6 +160,7 @@ def enhanced_async_context_manager[**P, T_co](
159
160
  make_agcm = asynccontextmanager(func)
160
161
 
161
162
  @asynccontextmanager
163
+ @wraps(func)
162
164
  async def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncIterator[T_co]:
163
165
  agcm = make_agcm(*args, **kwargs)
164
166
  sigabrt0 = _swap_handler(SIGABRT, agcm) if sigabrt else None
@@ -210,7 +212,7 @@ def _make_handler(
210
212
  _ = loop.call_soon_threadsafe(
211
213
  create_task, agcm.__aexit__(None, None, None)
212
214
  )
213
- case _ as never:
215
+ case never:
214
216
  assert_never(never)
215
217
  if callable(orig_handler): # pragma: no cover
216
218
  orig_handler(signum, frame)
utilities/contextvars.py CHANGED
@@ -1,6 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from contextlib import contextmanager
3
4
  from contextvars import ContextVar
5
+ from typing import TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from collections.abc import Iterator
9
+
4
10
 
5
11
  ##
6
12
 
@@ -19,4 +25,17 @@ def set_global_breakpoint() -> None:
19
25
  _ = _GLOBAL_BREAKPOINT.set(True)
20
26
 
21
27
 
22
- __all__ = ["global_breakpoint", "set_global_breakpoint"]
28
+ ##
29
+
30
+
31
+ @contextmanager
32
+ def yield_set_context(var: ContextVar[bool], /) -> Iterator[None]:
33
+ """Yield a context var as being set."""
34
+ token = var.set(True)
35
+ try:
36
+ yield
37
+ finally:
38
+ _ = var.reset(token)
39
+
40
+
41
+ __all__ = ["global_breakpoint", "set_global_breakpoint", "yield_set_context"]
utilities/cryptography.py CHANGED
@@ -11,18 +11,18 @@ _ENV_VAR = "FERNET_KEY"
11
11
 
12
12
  def encrypt(text: str, /, *, env_var: str = _ENV_VAR) -> bytes:
13
13
  """Encrypt a string."""
14
- return get_fernet(env_var=env_var).encrypt(text.encode())
14
+ return get_fernet(env_var).encrypt(text.encode())
15
15
 
16
16
 
17
17
  def decrypt(text: bytes, /, *, env_var: str = _ENV_VAR) -> str:
18
18
  """Encrypt a string."""
19
- return get_fernet(env_var=env_var).decrypt(text).decode()
19
+ return get_fernet(env_var).decrypt(text).decode()
20
20
 
21
21
 
22
22
  ##
23
23
 
24
24
 
25
- def get_fernet(*, env_var: str = _ENV_VAR) -> Fernet:
25
+ def get_fernet(env_var: str = _ENV_VAR, /) -> Fernet:
26
26
  """Get the Fernet key."""
27
27
  if (key := getenv(env_var)) is None:
28
28
  raise GetFernetError(env_var=env_var)
utilities/dataclasses.py CHANGED
@@ -6,11 +6,7 @@ from dataclasses import MISSING, dataclass, field, fields, replace
6
6
  from typing import TYPE_CHECKING, Any, Literal, assert_never, overload, override
7
7
 
8
8
  from utilities.errors import ImpossibleCaseError
9
- from utilities.functions import (
10
- get_class_name,
11
- is_dataclass_class,
12
- is_dataclass_instance,
13
- )
9
+ from utilities.functions import get_class_name
14
10
  from utilities.iterables import (
15
11
  OneStrEmptyError,
16
12
  OneStrNonUniqueError,
@@ -25,7 +21,7 @@ from utilities.parse import (
25
21
  serialize_object,
26
22
  )
27
23
  from utilities.re import ExtractGroupError, extract_group
28
- from utilities.sentinel import Sentinel, sentinel
24
+ from utilities.sentinel import Sentinel, is_sentinel, sentinel
29
25
  from utilities.text import (
30
26
  BRACKETS,
31
27
  LIST_SEPARATOR,
@@ -34,8 +30,8 @@ from utilities.text import (
34
30
  _SplitKeyValuePairsSplitError,
35
31
  split_key_value_pairs,
36
32
  )
37
- from utilities.types import SupportsLT
38
- from utilities.typing import get_type_hints
33
+ from utilities.types import MaybeType, SupportsLT
34
+ from utilities.typing import get_type_hints, is_dataclass_class, is_dataclass_instance
39
35
 
40
36
  if TYPE_CHECKING:
41
37
  from collections.abc import Callable, Iterable, Iterator
@@ -214,7 +210,7 @@ def is_nullable_lt[T: SupportsLT](x: T | None, y: T | None, /) -> bool | None:
214
210
  return True
215
211
  case 0:
216
212
  return None
217
- case _ as never:
213
+ case never:
218
214
  assert_never(never)
219
215
 
220
216
 
@@ -275,8 +271,7 @@ def mapping_to_dataclass[T: Dataclass](
275
271
  default = {
276
272
  f.name
277
273
  for f in fields_use
278
- if (not isinstance(f.default, Sentinel))
279
- or (not isinstance(f.default_factory, Sentinel))
274
+ if (not is_sentinel(f.default)) or (not is_sentinel(f.default_factory))
280
275
  }
281
276
  have = set(field_names_to_values) | default
282
277
  missing = {f.name for f in fields_use} - have
@@ -434,12 +429,10 @@ def replace_non_sentinel[T: Dataclass](
434
429
  """Replace attributes on a dataclass, filtering out sentinel values."""
435
430
  if in_place:
436
431
  for k, v in kwargs.items():
437
- if not isinstance(v, Sentinel):
432
+ if not is_sentinel(v):
438
433
  setattr(obj, k, v)
439
434
  return None
440
- return replace(
441
- obj, **{k: v for k, v in kwargs.items() if not isinstance(v, Sentinel)}
442
- )
435
+ return replace(obj, **{k: v for k, v in kwargs.items() if not is_sentinel(v)})
443
436
 
444
437
 
445
438
  ##
@@ -520,7 +513,7 @@ def parse_dataclass[T: Dataclass](
520
513
  )
521
514
  case Mapping() as keys_to_serializes:
522
515
  ...
523
- case _ as never:
516
+ case never:
524
517
  assert_never(never)
525
518
  fields = list(
526
519
  yield_fields(
@@ -833,7 +826,7 @@ def yield_fields(
833
826
  warn_name_errors: bool = False,
834
827
  ) -> Iterator[_YieldFieldsClass[Any]]: ...
835
828
  def yield_fields(
836
- obj: Dataclass | type[Dataclass],
829
+ obj: MaybeType[Dataclass],
837
830
  /,
838
831
  *,
839
832
  globalns: StrMapping | None = None,
@@ -912,17 +905,11 @@ class _YieldFieldsInstance[T]:
912
905
  extra: Mapping[type[U], Callable[[U, U], bool]] | None = None,
913
906
  ) -> bool:
914
907
  """Check if the field value equals its default."""
915
- if isinstance(self.default, Sentinel) and isinstance(
916
- self.default_factory, Sentinel
917
- ):
908
+ if is_sentinel(self.default) and is_sentinel(self.default_factory):
918
909
  return False
919
- if (not isinstance(self.default, Sentinel)) and isinstance(
920
- self.default_factory, Sentinel
921
- ):
910
+ if (not is_sentinel(self.default)) and is_sentinel(self.default_factory):
922
911
  expected = self.default
923
- elif isinstance(self.default, Sentinel) and (
924
- not isinstance(self.default_factory, Sentinel)
925
- ):
912
+ elif is_sentinel(self.default) and (not is_sentinel(self.default_factory)):
926
913
  expected = self.default_factory()
927
914
  else: # pragma: no cover
928
915
  raise ImpossibleCaseError(
@@ -1002,7 +989,7 @@ def _empty_error_str_core(
1002
989
  return f"any field starting with {key!r}"
1003
990
  case True, False:
1004
991
  return f"any field starting with {key!r} (modulo case)"
1005
- case _ as never:
992
+ case never:
1006
993
  assert_never(never)
1007
994
 
1008
995
 
@@ -1043,7 +1030,7 @@ def _non_unique_error_str_core(
1043
1030
  head_msg = f"exactly one field starting with {key!r}"
1044
1031
  case True, False:
1045
1032
  head_msg = f"exactly one field starting with {key!r} (modulo case)"
1046
- case _ as never:
1033
+ case never:
1047
1034
  assert_never(never)
1048
1035
  return f"{head_msg}; got {first!r}, {second!r} and perhaps more"
1049
1036