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/typing.py CHANGED
@@ -1,19 +1,25 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import datetime as dt
4
- from collections.abc import Mapping, Sequence
5
- from dataclasses import dataclass
4
+ from collections.abc import Callable, Iterable, Mapping, Sequence
5
+ from dataclasses import dataclass, is_dataclass
6
+ from functools import partial
6
7
  from itertools import chain
7
8
  from pathlib import Path
9
+ from re import search
8
10
  from types import NoneType, UnionType
9
11
  from typing import (
10
12
  Any,
13
+ ForwardRef,
11
14
  Literal,
12
15
  NamedTuple,
16
+ NotRequired,
13
17
  Optional, # pyright: ignore[reportDeprecated]
14
18
  TypeAliasType,
15
19
  TypeGuard,
16
20
  Union, # pyright: ignore[reportDeprecated]
21
+ _TypedDictMeta, # pyright: ignore[reportAttributeAccessIssue]
22
+ cast,
17
23
  get_origin,
18
24
  overload,
19
25
  override,
@@ -23,6 +29,7 @@ from typing import get_type_hints as _get_type_hints
23
29
  from uuid import UUID
24
30
  from warnings import warn
25
31
 
32
+ import whenever
26
33
  from whenever import (
27
34
  Date,
28
35
  DateDelta,
@@ -35,7 +42,13 @@ from whenever import (
35
42
 
36
43
  from utilities.iterables import unique_everseen
37
44
  from utilities.sentinel import Sentinel
38
- from utilities.types import StrMapping
45
+ from utilities.types import (
46
+ Dataclass,
47
+ StrMapping,
48
+ StrStrMapping,
49
+ TupleOrStrMapping,
50
+ TypeLike,
51
+ )
39
52
 
40
53
 
41
54
  def get_args(obj: Any, /, *, optional_drop_none: bool = False) -> tuple[Any, ...]:
@@ -51,6 +64,18 @@ def get_args(obj: Any, /, *, optional_drop_none: bool = False) -> tuple[Any, ...
51
64
  ##
52
65
 
53
66
 
67
+ def get_forward_ref_args(obj: Any, /) -> StrStrMapping:
68
+ """Get the forward args."""
69
+ return {
70
+ k: v.__forward_arg__
71
+ for k, v in obj.__annotations__.items()
72
+ if isinstance(v, ForwardRef)
73
+ }
74
+
75
+
76
+ ##
77
+
78
+
54
79
  def get_literal_elements(obj: Any, /) -> list[Any]:
55
80
  """Get the elements of a literal annotation."""
56
81
  return _get_literal_elements_inner(obj)
@@ -122,7 +147,6 @@ def get_type_hints(
122
147
  warn_name_errors: bool = False,
123
148
  ) -> dict[str, Any]:
124
149
  """Get the type hints of an object."""
125
- result: dict[str, Any] = obj.__annotations__
126
150
  _ = {
127
151
  Date,
128
152
  DateDelta,
@@ -136,21 +160,31 @@ def get_type_hints(
136
160
  TimeDelta,
137
161
  UUID,
138
162
  ZonedDateTime,
139
- dt,
163
+ whenever.Date,
164
+ whenever.DateDelta,
165
+ whenever.DateTimeDelta,
166
+ whenever.PlainDateTime,
167
+ whenever.Time,
168
+ whenever.TimeDelta,
169
+ whenever.ZonedDateTime,
140
170
  }
141
171
  globalns_use = globals() | ({} if globalns is None else dict(globalns))
142
172
  localns_use = {} if localns is None else dict(localns)
173
+ result: dict[str, Any] = obj.__annotations__
174
+ result = result | dict(get_forward_ref_args(obj))
143
175
  try:
144
- hints = _get_type_hints(obj, globalns=globalns_use, localns=localns_use)
176
+ hints = _get_type_hints(
177
+ obj, globalns=globalns_use, localns=localns_use, include_extras=True
178
+ )
145
179
  except NameError as error:
146
180
  if warn_name_errors:
147
181
  warn(f"Error getting type hints for {obj!r}; {error}", stacklevel=2)
148
182
  else:
149
- result.update({
183
+ result = result | {
150
184
  key: value
151
185
  for key, value in hints.items()
152
186
  if (key not in result) or isinstance(result[key], str)
153
- })
187
+ }
154
188
  return result
155
189
 
156
190
 
@@ -195,6 +229,22 @@ class _GetUnionTypeClassesInternalTypeError(GetUnionTypeClassesError):
195
229
  ##
196
230
 
197
231
 
232
+ def is_dataclass_class(obj: Any, /) -> TypeGuard[type[Dataclass]]:
233
+ """Check if an object is a dataclass."""
234
+ return isinstance(obj, type) and is_dataclass(obj)
235
+
236
+
237
+ ##
238
+
239
+
240
+ def is_dataclass_instance(obj: Any, /) -> TypeGuard[Dataclass]:
241
+ """Check if an object is an instance of a dataclass."""
242
+ return (not isinstance(obj, type)) and is_dataclass(obj)
243
+
244
+
245
+ ##
246
+
247
+
198
248
  def is_dict_type(obj: Any, /) -> bool:
199
249
  """Check if an object is a dict type annotation."""
200
250
  return _is_annotation_of_type(obj, dict)
@@ -212,50 +262,174 @@ def is_frozenset_type(obj: Any, /) -> bool:
212
262
 
213
263
 
214
264
  @overload
215
- def is_instance_gen[T](obj: Any, type_: type[T], /) -> TypeGuard[T]: ...
265
+ def is_instance_gen[T](
266
+ obj: Any,
267
+ type_: type[T],
268
+ /,
269
+ *,
270
+ globalns: StrMapping | None = None,
271
+ localns: StrMapping | None = None,
272
+ warn_name_errors: bool = False,
273
+ ) -> TypeGuard[T]: ...
216
274
  @overload
217
- def is_instance_gen[T1](obj: Any, type_: tuple[T1], /) -> TypeGuard[T1]: ...
275
+ def is_instance_gen[T1](
276
+ obj: Any,
277
+ type_: tuple[T1],
278
+ /,
279
+ *,
280
+ globalns: StrMapping | None = None,
281
+ localns: StrMapping | None = None,
282
+ warn_name_errors: bool = False,
283
+ ) -> TypeGuard[T1]: ...
218
284
  @overload
219
285
  def is_instance_gen[T1, T2](
220
- obj: Any, type_: tuple[T1, T2], /
286
+ obj: Any,
287
+ type_: tuple[T1, T2],
288
+ /,
289
+ *,
290
+ globalns: StrMapping | None = None,
291
+ localns: StrMapping | None = None,
292
+ warn_name_errors: bool = False,
221
293
  ) -> TypeGuard[T1 | T2]: ...
222
294
  @overload
223
295
  def is_instance_gen[T1, T2, T3](
224
- obj: Any, type_: tuple[T1, T2, T3], /
296
+ obj: Any,
297
+ type_: tuple[T1, T2, T3],
298
+ /,
299
+ *,
300
+ globalns: StrMapping | None = None,
301
+ localns: StrMapping | None = None,
302
+ warn_name_errors: bool = False,
225
303
  ) -> TypeGuard[T1 | T2 | T3]: ...
226
304
  @overload
227
305
  def is_instance_gen[T1, T2, T3, T4](
228
- obj: Any, type_: tuple[T1, T2, T3, T4], /
306
+ obj: Any,
307
+ type_: tuple[T1, T2, T3, T4],
308
+ /,
309
+ *,
310
+ globalns: StrMapping | None = None,
311
+ localns: StrMapping | None = None,
312
+ warn_name_errors: bool = False,
229
313
  ) -> TypeGuard[T1 | T2 | T3 | T4]: ...
230
314
  @overload
231
315
  def is_instance_gen[T1, T2, T3, T4, T5](
232
- obj: Any, type_: tuple[T1, T2, T3, T4, T5], /
316
+ obj: Any,
317
+ type_: tuple[T1, T2, T3, T4, T5],
318
+ /,
319
+ *,
320
+ globalns: StrMapping | None = None,
321
+ localns: StrMapping | None = None,
322
+ warn_name_errors: bool = False,
233
323
  ) -> TypeGuard[T1 | T2 | T3 | T4 | T5]: ...
234
324
  @overload
235
- def is_instance_gen(obj: Any, type_: Any, /) -> bool: ...
236
- def is_instance_gen(obj: Any, type_: Any, /) -> bool:
325
+ def is_instance_gen(
326
+ obj: Any,
327
+ type_: Any,
328
+ /,
329
+ *,
330
+ globalns: StrMapping | None = None,
331
+ localns: StrMapping | None = None,
332
+ warn_name_errors: bool = False,
333
+ ) -> bool: ...
334
+ def is_instance_gen(
335
+ obj: Any,
336
+ type_: Any,
337
+ /,
338
+ *,
339
+ globalns: StrMapping | None = None,
340
+ localns: StrMapping | None = None,
341
+ warn_name_errors: bool = False,
342
+ ) -> bool:
237
343
  """Check if an instance relationship holds, except bool<int."""
238
344
  # parent
239
345
  if isinstance(type_, tuple):
240
- return any(is_instance_gen(obj, t) for t in type_) # skipif-ci-and-not-windows
346
+ return any(
347
+ is_instance_gen(
348
+ obj,
349
+ t,
350
+ globalns=globalns,
351
+ localns=localns,
352
+ warn_name_errors=warn_name_errors,
353
+ )
354
+ for t in type_
355
+ )
241
356
  if is_literal_type(type_):
242
357
  return obj in get_args(type_)
243
358
  if is_union_type(type_):
244
- return any(is_instance_gen(obj, t) for t in get_args(type_))
359
+ return any(
360
+ is_instance_gen(
361
+ obj,
362
+ t,
363
+ globalns=globalns,
364
+ localns=localns,
365
+ warn_name_errors=warn_name_errors,
366
+ )
367
+ for t in get_args(type_)
368
+ )
245
369
  # tuple vs tuple
246
370
  if isinstance(obj, tuple) and is_tuple_type(type_):
247
371
  type_args = get_args(type_)
248
372
  return (len(obj) == len(type_args)) and all(
249
- is_instance_gen(o, t) for o, t in zip(obj, type_args, strict=True)
373
+ is_instance_gen(
374
+ o,
375
+ t,
376
+ globalns=globalns,
377
+ localns=localns,
378
+ warn_name_errors=warn_name_errors,
379
+ )
380
+ for o, t in zip(obj, type_args, strict=True)
250
381
  )
251
382
  if isinstance(obj, tuple) is not is_tuple_type(type_):
252
383
  return False
253
384
  # basic
385
+ if isinstance(type_, _TypedDictMeta):
386
+ return _is_instance_typed_dict(
387
+ obj,
388
+ type_,
389
+ globalns=globalns,
390
+ localns=localns,
391
+ warn_name_errors=warn_name_errors,
392
+ )
254
393
  if isinstance(type_, type):
255
394
  return any(_is_instance_gen_type(obj, t) for t in get_type_classes(type_))
256
395
  raise IsInstanceGenError(obj=obj, type_=type_)
257
396
 
258
397
 
398
+ def _is_instance_typed_dict[T: _TypedDictMeta](
399
+ obj: Any,
400
+ type_: type[T],
401
+ /,
402
+ *,
403
+ globalns: StrMapping | None = None,
404
+ localns: StrMapping | None = None,
405
+ warn_name_errors: bool = False,
406
+ ) -> TypeGuard[T]:
407
+ if not isinstance(obj, dict):
408
+ return False
409
+ if not all(isinstance(k, str) for k in obj):
410
+ return False
411
+ obj = cast("dict[str, Any]", obj)
412
+ hints = get_type_hints(
413
+ type_, globalns=globalns, localns=localns, warn_name_errors=warn_name_errors
414
+ )
415
+ optional = {
416
+ k for k, v in type_.__annotations__.items() if is_not_required_annotation(v)
417
+ }
418
+ required = {k: v for k, v in hints.items() if k not in optional}
419
+ if not set(obj).issuperset(required):
420
+ return False
421
+ return all(
422
+ is_instance_gen(
423
+ obj[k],
424
+ required[k],
425
+ globalns=globalns,
426
+ localns=localns,
427
+ warn_name_errors=warn_name_errors,
428
+ )
429
+ for k in required
430
+ )
431
+
432
+
259
433
  def _is_instance_gen_type[T](obj: Any, type_: type[T], /) -> TypeGuard[T]:
260
434
  return (
261
435
  isinstance(obj, type_)
@@ -285,6 +459,101 @@ class IsInstanceGenError(Exception):
285
459
  ##
286
460
 
287
461
 
462
+ @overload
463
+ def is_iterable_of[T](
464
+ obj: Any,
465
+ cls: type[T],
466
+ /,
467
+ *,
468
+ globalns: StrMapping | None = None,
469
+ localns: StrMapping | None = None,
470
+ warn_name_errors: bool = False,
471
+ ) -> TypeGuard[Iterable[T]]: ...
472
+ @overload
473
+ def is_iterable_of[T1](
474
+ obj: Any,
475
+ cls: tuple[type[T1]],
476
+ /,
477
+ *,
478
+ globalns: StrMapping | None = None,
479
+ localns: StrMapping | None = None,
480
+ warn_name_errors: bool = False,
481
+ ) -> TypeGuard[Iterable[T1]]: ...
482
+ @overload
483
+ def is_iterable_of[T1, T2](
484
+ obj: Any,
485
+ cls: tuple[type[T1], type[T2]],
486
+ /,
487
+ *,
488
+ globalns: StrMapping | None = None,
489
+ localns: StrMapping | None = None,
490
+ warn_name_errors: bool = False,
491
+ ) -> TypeGuard[Iterable[T1 | T2]]: ...
492
+ @overload
493
+ def is_iterable_of[T1, T2, T3](
494
+ obj: Any,
495
+ cls: tuple[type[T1], type[T2], type[T3]],
496
+ /,
497
+ *,
498
+ globalns: StrMapping | None = None,
499
+ localns: StrMapping | None = None,
500
+ warn_name_errors: bool = False,
501
+ ) -> TypeGuard[Iterable[T1 | T2 | T3]]: ...
502
+ @overload
503
+ def is_iterable_of[T1, T2, T3, T4](
504
+ obj: Any,
505
+ cls: tuple[type[T1], type[T2], type[T3], type[T4]],
506
+ /,
507
+ *,
508
+ globalns: StrMapping | None = None,
509
+ localns: StrMapping | None = None,
510
+ warn_name_errors: bool = False,
511
+ ) -> TypeGuard[Iterable[T1 | T2 | T3 | T4]]: ...
512
+ @overload
513
+ def is_iterable_of[T1, T2, T3, T4, T5](
514
+ obj: Any,
515
+ cls: tuple[type[T1], type[T2], type[T3], type[T4], type[T5]],
516
+ /,
517
+ *,
518
+ globalns: StrMapping | None = None,
519
+ localns: StrMapping | None = None,
520
+ warn_name_errors: bool = False,
521
+ ) -> TypeGuard[Iterable[T1 | T2 | T3 | T4 | T5]]: ...
522
+ @overload
523
+ def is_iterable_of[T](
524
+ obj: Any,
525
+ cls: TypeLike[T],
526
+ /,
527
+ *,
528
+ globalns: StrMapping | None = None,
529
+ localns: StrMapping | None = None,
530
+ warn_name_errors: bool = False,
531
+ ) -> TypeGuard[Iterable[T]]: ...
532
+ def is_iterable_of[T](
533
+ obj: Any,
534
+ cls: TypeLike[T],
535
+ /,
536
+ *,
537
+ globalns: StrMapping | None = None,
538
+ localns: StrMapping | None = None,
539
+ warn_name_errors: bool = False,
540
+ ) -> TypeGuard[Iterable[T]]:
541
+ """Check if an object is a iterable of tuple or string mappings."""
542
+ return isinstance(obj, Iterable) and all(
543
+ is_instance_gen(
544
+ o,
545
+ cls,
546
+ globalns=globalns,
547
+ localns=localns,
548
+ warn_name_errors=warn_name_errors,
549
+ )
550
+ for o in obj
551
+ )
552
+
553
+
554
+ ##
555
+
556
+
288
557
  def is_list_type(obj: Any, /) -> bool:
289
558
  """Check if an object is a list type annotation."""
290
559
  return _is_annotation_of_type(obj, list)
@@ -331,6 +600,30 @@ def _is_namedtuple_core(obj: Any, /) -> bool:
331
600
  ##
332
601
 
333
602
 
603
+ def is_not_required_annotation(obj: Any) -> bool:
604
+ """Check if an annotation is not required."""
605
+ if is_not_required_type(obj):
606
+ return True
607
+ match obj:
608
+ case str() as text:
609
+ return bool(search("NotRequired", text))
610
+ case ForwardRef() as fr:
611
+ return is_not_required_annotation(fr.__forward_arg__)
612
+ case _:
613
+ return False
614
+
615
+
616
+ ##
617
+
618
+
619
+ def is_not_required_type(obj: Any, /) -> bool:
620
+ """Check if an object is a not-required type annotation."""
621
+ return (obj is NotRequired) or _is_annotation_of_type(obj, NotRequired)
622
+
623
+
624
+ ##
625
+
626
+
334
627
  def is_optional_type(obj: Any, /) -> bool:
335
628
  """Check if an object is an optional type annotation."""
336
629
  is_optional = _is_annotation_of_type(obj, Optional) # pyright: ignore[reportDeprecated]
@@ -342,6 +635,104 @@ def is_optional_type(obj: Any, /) -> bool:
342
635
  ##
343
636
 
344
637
 
638
+ @overload
639
+ def is_sequence_of[T](
640
+ obj: Any,
641
+ cls: type[T],
642
+ /,
643
+ *,
644
+ globalns: StrMapping | None = None,
645
+ localns: StrMapping | None = None,
646
+ warn_name_errors: bool = False,
647
+ ) -> TypeGuard[Sequence[T]]: ...
648
+ @overload
649
+ def is_sequence_of[T1](
650
+ obj: Any,
651
+ cls: tuple[type[T1]],
652
+ /,
653
+ *,
654
+ globalns: StrMapping | None = None,
655
+ localns: StrMapping | None = None,
656
+ warn_name_errors: bool = False,
657
+ ) -> TypeGuard[Sequence[T1]]: ...
658
+ @overload
659
+ def is_sequence_of[T1, T2](
660
+ obj: Any,
661
+ cls: tuple[type[T1], type[T2]],
662
+ /,
663
+ *,
664
+ globalns: StrMapping | None = None,
665
+ localns: StrMapping | None = None,
666
+ warn_name_errors: bool = False,
667
+ ) -> TypeGuard[Sequence[T1 | T2]]: ...
668
+ @overload
669
+ def is_sequence_of[T1, T2, T3](
670
+ obj: Any,
671
+ cls: tuple[type[T1], type[T2], type[T3]],
672
+ /,
673
+ *,
674
+ globalns: StrMapping | None = None,
675
+ localns: StrMapping | None = None,
676
+ warn_name_errors: bool = False,
677
+ ) -> TypeGuard[Sequence[T1 | T2 | T3]]: ...
678
+ @overload
679
+ def is_sequence_of[T1, T2, T3, T4](
680
+ obj: Any,
681
+ cls: tuple[type[T1], type[T2], type[T3], type[T4]],
682
+ /,
683
+ *,
684
+ globalns: StrMapping | None = None,
685
+ localns: StrMapping | None = None,
686
+ warn_name_errors: bool = False,
687
+ ) -> TypeGuard[Sequence[T1 | T2 | T3 | T4]]: ...
688
+ @overload
689
+ def is_sequence_of[T1, T2, T3, T4, T5](
690
+ obj: Any,
691
+ cls: tuple[type[T1], type[T2], type[T3], type[T4], type[T5]],
692
+ /,
693
+ *,
694
+ globalns: StrMapping | None = None,
695
+ localns: StrMapping | None = None,
696
+ warn_name_errors: bool = False,
697
+ ) -> TypeGuard[Sequence[T1 | T2 | T3 | T4 | T5]]: ...
698
+ @overload
699
+ def is_sequence_of[T](
700
+ obj: Any,
701
+ cls: TypeLike[T],
702
+ /,
703
+ *,
704
+ globalns: StrMapping | None = None,
705
+ localns: StrMapping | None = None,
706
+ warn_name_errors: bool = False,
707
+ ) -> TypeGuard[Sequence[T]]: ...
708
+ def is_sequence_of[T](
709
+ obj: Any,
710
+ cls: TypeLike[T],
711
+ /,
712
+ *,
713
+ globalns: StrMapping | None = None,
714
+ localns: StrMapping | None = None,
715
+ warn_name_errors: bool = False,
716
+ ) -> TypeGuard[Sequence[T]]:
717
+ """Check if an object is a sequence of tuple or string mappings."""
718
+ return isinstance(obj, Sequence) and is_iterable_of(
719
+ obj, cls, globalns=globalns, localns=localns, warn_name_errors=warn_name_errors
720
+ )
721
+
722
+
723
+ ##
724
+
725
+
726
+ def is_sequence_of_tuple_or_str_mapping(
727
+ obj: Any, /
728
+ ) -> TypeGuard[Sequence[TupleOrStrMapping]]:
729
+ """Check if an object is a sequence of tuple or string mappings."""
730
+ return isinstance(obj, Sequence) and all(map(is_tuple_or_str_mapping, obj))
731
+
732
+
733
+ ##
734
+
735
+
345
736
  def is_sequence_type(obj: Any, /) -> bool:
346
737
  """Check if an object is a sequence type annotation."""
347
738
  return _is_annotation_of_type(obj, Sequence)
@@ -358,6 +749,14 @@ def is_set_type(obj: Any, /) -> bool:
358
749
  ##
359
750
 
360
751
 
752
+ def is_string_mapping(obj: Any, /) -> TypeGuard[StrMapping]:
753
+ """Check if an object is a string mapping."""
754
+ return isinstance(obj, Mapping) and is_iterable_of(obj, str)
755
+
756
+
757
+ ##
758
+
759
+
361
760
  @overload
362
761
  def is_subclass_gen[T](cls: type[Any], parent: type[T], /) -> TypeGuard[type[T]]: ...
363
762
  @overload
@@ -443,6 +842,22 @@ class IsSubclassGenError(Exception):
443
842
  ##
444
843
 
445
844
 
845
+ def is_tuple(obj: Any, /) -> TypeGuard[tuple[Any, ...]]:
846
+ """Check if an object is a tuple."""
847
+ return isinstance(obj, tuple)
848
+
849
+
850
+ ##
851
+
852
+
853
+ def is_tuple_or_str_mapping(obj: Any, /) -> TypeGuard[TupleOrStrMapping]:
854
+ """Check if an object is a tuple or string mapping."""
855
+ return is_tuple(obj) or is_string_mapping(obj)
856
+
857
+
858
+ ##
859
+
860
+
446
861
  def is_tuple_type(obj: Any, /) -> bool:
447
862
  """Check if an object is a tuple type annotation."""
448
863
  return _is_annotation_of_type(obj, tuple)
@@ -467,6 +882,40 @@ def _is_annotation_of_type(obj: Any, origin: Any, /) -> bool:
467
882
  )
468
883
 
469
884
 
885
+ ##
886
+
887
+
888
+ @overload
889
+ def make_isinstance[T](cls: type[T], /) -> Callable[[Any], TypeGuard[T]]: ...
890
+ @overload
891
+ def make_isinstance[T1](cls: tuple[type[T1]], /) -> Callable[[Any], TypeGuard[T1]]: ...
892
+ @overload
893
+ def make_isinstance[T1, T2](
894
+ cls: tuple[type[T1], type[T2]], /
895
+ ) -> Callable[[Any], TypeGuard[T1 | T2]]: ...
896
+ @overload
897
+ def make_isinstance[T1, T2, T3](
898
+ cls: tuple[type[T1], type[T2], type[T3]], /
899
+ ) -> Callable[[Any], TypeGuard[T1 | T2 | T3]]: ...
900
+ @overload
901
+ def make_isinstance[T1, T2, T3, T4](
902
+ cls: tuple[type[T1], type[T2], type[T3], type[T4]], /
903
+ ) -> Callable[[Any], TypeGuard[T1 | T2 | T3 | T4]]: ...
904
+ @overload
905
+ def make_isinstance[T1, T2, T3, T4, T5](
906
+ cls: tuple[type[T1], type[T2], type[T3], type[T4], type[T5]], /
907
+ ) -> Callable[[Any], TypeGuard[T1 | T2 | T3 | T4 | T5]]: ...
908
+ @overload
909
+ def make_isinstance[T](cls: TypeLike[T], /) -> Callable[[Any], TypeGuard[T]]: ...
910
+ def make_isinstance[T](cls: TypeLike[T], /) -> Callable[[Any], TypeGuard[T]]:
911
+ """Make a curried `isinstance` function."""
912
+ return partial(_make_instance_core, cls=cls)
913
+
914
+
915
+ def _make_instance_core[T](obj: Any, /, *, cls: TypeLike[T]) -> TypeGuard[T]:
916
+ return is_instance_gen(obj, cls)
917
+
918
+
470
919
  __all__ = [
471
920
  "GetTypeClassesError",
472
921
  "GetUnionTypeClassesError",
@@ -476,18 +925,29 @@ __all__ = [
476
925
  "get_type_classes",
477
926
  "get_type_hints",
478
927
  "get_union_type_classes",
928
+ "is_dataclass_class",
929
+ "is_dataclass_instance",
479
930
  "is_dict_type",
480
931
  "is_frozenset_type",
481
932
  "is_instance_gen",
933
+ "is_iterable_of",
482
934
  "is_list_type",
483
935
  "is_literal_type",
484
936
  "is_mapping_type",
485
937
  "is_namedtuple_class",
486
938
  "is_namedtuple_instance",
939
+ "is_not_required_annotation",
940
+ "is_not_required_type",
487
941
  "is_optional_type",
942
+ "is_sequence_of",
943
+ "is_sequence_of_tuple_or_str_mapping",
488
944
  "is_sequence_type",
489
945
  "is_set_type",
946
+ "is_string_mapping",
490
947
  "is_subclass_gen",
948
+ "is_tuple",
949
+ "is_tuple_or_str_mapping",
491
950
  "is_tuple_type",
492
951
  "is_union_type",
952
+ "make_isinstance",
493
953
  ]