haiway 0.10.14__py3-none-any.whl → 0.10.16__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 (43) hide show
  1. haiway/__init__.py +111 -0
  2. haiway/context/__init__.py +27 -0
  3. haiway/context/access.py +615 -0
  4. haiway/context/disposables.py +78 -0
  5. haiway/context/identifier.py +92 -0
  6. haiway/context/logging.py +176 -0
  7. haiway/context/metrics.py +165 -0
  8. haiway/context/state.py +113 -0
  9. haiway/context/tasks.py +64 -0
  10. haiway/context/types.py +12 -0
  11. haiway/helpers/__init__.py +21 -0
  12. haiway/helpers/asynchrony.py +225 -0
  13. haiway/helpers/caching.py +326 -0
  14. haiway/helpers/metrics.py +459 -0
  15. haiway/helpers/retries.py +223 -0
  16. haiway/helpers/throttling.py +133 -0
  17. haiway/helpers/timeouted.py +112 -0
  18. haiway/helpers/tracing.py +137 -0
  19. haiway/py.typed +0 -0
  20. haiway/state/__init__.py +12 -0
  21. haiway/state/attributes.py +747 -0
  22. haiway/state/path.py +524 -0
  23. haiway/state/requirement.py +229 -0
  24. haiway/state/structure.py +414 -0
  25. haiway/state/validation.py +468 -0
  26. haiway/types/__init__.py +14 -0
  27. haiway/types/default.py +108 -0
  28. haiway/types/frozen.py +5 -0
  29. haiway/types/missing.py +95 -0
  30. haiway/utils/__init__.py +28 -0
  31. haiway/utils/always.py +61 -0
  32. haiway/utils/collections.py +185 -0
  33. haiway/utils/env.py +230 -0
  34. haiway/utils/freezing.py +28 -0
  35. haiway/utils/logs.py +57 -0
  36. haiway/utils/mimic.py +77 -0
  37. haiway/utils/noop.py +24 -0
  38. haiway/utils/queue.py +82 -0
  39. {haiway-0.10.14.dist-info → haiway-0.10.16.dist-info}/METADATA +1 -1
  40. haiway-0.10.16.dist-info/RECORD +42 -0
  41. haiway-0.10.14.dist-info/RECORD +0 -4
  42. {haiway-0.10.14.dist-info → haiway-0.10.16.dist-info}/WHEEL +0 -0
  43. {haiway-0.10.14.dist-info → haiway-0.10.16.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,747 @@
1
+ import types
2
+ import typing
3
+ from collections.abc import Callable, Mapping, MutableMapping, Sequence
4
+ from types import GenericAlias, NoneType, UnionType
5
+ from typing import (
6
+ Any,
7
+ ClassVar,
8
+ ForwardRef,
9
+ Generic,
10
+ Literal,
11
+ ParamSpec,
12
+ Self,
13
+ TypeAliasType,
14
+ TypeVar,
15
+ TypeVarTuple,
16
+ _GenericAlias, # pyright: ignore
17
+ get_args,
18
+ get_origin,
19
+ get_type_hints,
20
+ is_typeddict,
21
+ overload,
22
+ )
23
+
24
+ from haiway import types as haiway_types
25
+ from haiway.types import MISSING, Missing
26
+
27
+ __all__ = [
28
+ "AttributeAnnotation",
29
+ "attribute_annotations",
30
+ "resolve_attribute_annotation",
31
+ ]
32
+
33
+
34
+ class AttributeAnnotation:
35
+ def __init__(
36
+ self,
37
+ *,
38
+ origin: Any,
39
+ arguments: Sequence[Any] | None = None,
40
+ required: bool = True,
41
+ extra: Mapping[str, Any] | None = None,
42
+ ) -> None:
43
+ self.origin: Any = origin
44
+ self.arguments: Sequence[Any]
45
+ if arguments is None:
46
+ self.arguments = ()
47
+
48
+ else:
49
+ self.arguments = arguments
50
+
51
+ self.required: bool = required
52
+ self.extra: Mapping[str, Any]
53
+ if extra is None:
54
+ self.extra = {}
55
+
56
+ else:
57
+ self.extra = extra
58
+
59
+ def update_required(
60
+ self,
61
+ required: bool,
62
+ /,
63
+ ) -> Self:
64
+ self.required = self.required and required
65
+
66
+ return self
67
+
68
+ def __str__(self) -> str:
69
+ if alias := self.extra.get("TYPE_ALIAS"):
70
+ return alias
71
+
72
+ origin_str: str = getattr(self.origin, "__name__", str(self.origin))
73
+ arguments_str: str
74
+ if self.arguments:
75
+ arguments_str = "[" + ", ".join(str(arg) for arg in self.arguments) + "]"
76
+
77
+ else:
78
+ arguments_str = ""
79
+
80
+ if module := getattr(self.origin, "__module__", None):
81
+ return f"{module}.{origin_str}{arguments_str}"
82
+
83
+ else:
84
+ return f"{origin_str}{arguments_str}"
85
+
86
+
87
+ def attribute_annotations(
88
+ cls: type[Any],
89
+ /,
90
+ type_parameters: Mapping[str, Any],
91
+ ) -> Mapping[str, AttributeAnnotation]:
92
+ self_annotation = AttributeAnnotation(
93
+ origin=cls,
94
+ # ignore arguments here, State (and draive.DataModel) will have them resolved at this stage
95
+ arguments=[],
96
+ )
97
+
98
+ # ignore args_keys here, State (and draive.DataModel) will have them resolved at this stage
99
+ recursion_guard: MutableMapping[str, AttributeAnnotation] = {
100
+ _recursion_key(cls, default=str(self_annotation)): self_annotation
101
+ }
102
+
103
+ attributes: dict[str, AttributeAnnotation] = {}
104
+ for key, annotation in get_type_hints(cls, localns={cls.__name__: cls}).items():
105
+ # do not include private or special items
106
+ if key.startswith("_"):
107
+ continue
108
+
109
+ # do not include ClassVars
110
+ if (get_origin(annotation) or annotation) is ClassVar:
111
+ continue
112
+
113
+ attributes[key] = resolve_attribute_annotation(
114
+ annotation,
115
+ type_parameters=type_parameters,
116
+ module=cls.__module__,
117
+ self_annotation=self_annotation,
118
+ recursion_guard=recursion_guard,
119
+ )
120
+
121
+ return attributes
122
+
123
+
124
+ def _resolve_none(
125
+ annotation: Any,
126
+ ) -> AttributeAnnotation:
127
+ return AttributeAnnotation(origin=NoneType)
128
+
129
+
130
+ def _resolve_missing(
131
+ annotation: Any,
132
+ ) -> AttributeAnnotation:
133
+ # special case - attributes marked as missing are not required
134
+ # Missing does not work properly within TypedDict though
135
+ return AttributeAnnotation(
136
+ origin=Missing,
137
+ required=False,
138
+ )
139
+
140
+
141
+ def _resolve_literal(
142
+ annotation: Any,
143
+ ) -> AttributeAnnotation:
144
+ return AttributeAnnotation(
145
+ origin=Literal,
146
+ arguments=get_args(annotation),
147
+ )
148
+
149
+
150
+ def _resolve_forward_ref(
151
+ annotation: ForwardRef | str,
152
+ /,
153
+ module: str,
154
+ type_parameters: Mapping[str, Any],
155
+ self_annotation: AttributeAnnotation | None,
156
+ recursion_guard: MutableMapping[str, AttributeAnnotation],
157
+ ) -> AttributeAnnotation:
158
+ forward_ref: ForwardRef
159
+ match annotation:
160
+ case str() as string:
161
+ forward_ref = ForwardRef(string, module=module)
162
+
163
+ case reference:
164
+ forward_ref = reference
165
+
166
+ if evaluated := forward_ref._evaluate(
167
+ globalns=None,
168
+ localns=None,
169
+ recursive_guard=frozenset(),
170
+ ):
171
+ return resolve_attribute_annotation(
172
+ evaluated,
173
+ type_parameters=type_parameters,
174
+ module=module,
175
+ self_annotation=self_annotation,
176
+ recursion_guard=recursion_guard,
177
+ )
178
+
179
+ else:
180
+ raise RuntimeError(f"Cannot resolve annotation of {annotation}")
181
+
182
+
183
+ def _resolve_generic_alias( # noqa: PLR0911, PLR0912
184
+ annotation: GenericAlias,
185
+ /,
186
+ module: str,
187
+ type_parameters: Mapping[str, Any],
188
+ self_annotation: AttributeAnnotation | None,
189
+ recursion_guard: MutableMapping[str, AttributeAnnotation],
190
+ ) -> AttributeAnnotation:
191
+ match get_origin(annotation):
192
+ case TypeAliasType() as alias: # pyright: ignore[reportUnnecessaryComparison]
193
+ return _resolve_type_alias(
194
+ alias,
195
+ type_parameters={
196
+ # verify if we should pass all parameters
197
+ param.__name__: get_args(annotation)[idx]
198
+ for idx, param in enumerate(alias.__type_params__)
199
+ },
200
+ module=module,
201
+ self_annotation=self_annotation,
202
+ recursion_guard=recursion_guard,
203
+ )
204
+
205
+ case origin if issubclass(origin, Generic):
206
+ match origin.__class_getitem__( # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
207
+ tuple(
208
+ type_parameters.get(
209
+ arg.__name__,
210
+ arg.__bound__ or Any,
211
+ )
212
+ if isinstance(arg, TypeVar)
213
+ else arg
214
+ for arg in get_args(annotation)
215
+ )
216
+ ):
217
+ case GenericAlias() as generic_alias:
218
+ resolved_attribute = AttributeAnnotation(origin=generic_alias.__origin__)
219
+ if recursion_key := _recursion_key(generic_alias):
220
+ if recursive := recursion_guard.get(recursion_key):
221
+ return recursive
222
+
223
+ else:
224
+ recursion_guard[recursion_key] = resolved_attribute
225
+
226
+ resolved_attribute.arguments = [
227
+ resolve_attribute_annotation(
228
+ argument,
229
+ type_parameters=type_parameters,
230
+ module=module,
231
+ self_annotation=self_annotation,
232
+ recursion_guard=recursion_guard,
233
+ )
234
+ for argument in get_args(generic_alias)
235
+ ]
236
+
237
+ return resolved_attribute
238
+
239
+ # use resolved type if it is not an alias again
240
+ case resolved: # pyright: ignore
241
+ resolved_attribute = AttributeAnnotation(origin=resolved)
242
+
243
+ if recursion_key := _recursion_key(origin):
244
+ if recursive := recursion_guard.get(recursion_key):
245
+ return recursive
246
+
247
+ else:
248
+ recursion_guard[recursion_key] = resolved_attribute
249
+
250
+ resolved_attribute.arguments = [
251
+ resolve_attribute_annotation(
252
+ argument,
253
+ type_parameters=type_parameters,
254
+ module=module,
255
+ self_annotation=self_annotation,
256
+ recursion_guard=recursion_guard,
257
+ )
258
+ for argument in get_args(annotation)
259
+ ]
260
+
261
+ return resolved_attribute
262
+
263
+ case origin:
264
+ resolved_attribute = AttributeAnnotation(origin=origin)
265
+
266
+ if recursion_key := _recursion_key(origin):
267
+ if recursive := recursion_guard.get(recursion_key):
268
+ return recursive
269
+
270
+ resolved_attribute.arguments = [
271
+ resolve_attribute_annotation(
272
+ argument,
273
+ type_parameters=type_parameters,
274
+ module=module,
275
+ self_annotation=self_annotation,
276
+ recursion_guard=recursion_guard,
277
+ )
278
+ for argument in get_args(annotation)
279
+ ]
280
+
281
+ return resolved_attribute
282
+
283
+
284
+ def _resolve_special_generic_alias(
285
+ annotation: Any,
286
+ /,
287
+ module: str,
288
+ type_parameters: Mapping[str, Any],
289
+ self_annotation: AttributeAnnotation | None,
290
+ recursion_guard: MutableMapping[str, AttributeAnnotation],
291
+ ) -> AttributeAnnotation:
292
+ origin: type[Any] = get_origin(annotation)
293
+ resolved_attribute = AttributeAnnotation(origin=origin)
294
+
295
+ if recursion_key := _recursion_key(origin):
296
+ if recursive := recursion_guard.get(recursion_key):
297
+ return recursive
298
+
299
+ else:
300
+ recursion_guard[recursion_key] = resolved_attribute
301
+
302
+ resolved_attribute.arguments = [
303
+ resolve_attribute_annotation(
304
+ argument,
305
+ type_parameters=type_parameters,
306
+ module=module,
307
+ self_annotation=self_annotation,
308
+ recursion_guard=recursion_guard,
309
+ )
310
+ for argument in get_args(annotation)
311
+ ]
312
+
313
+ return resolved_attribute
314
+
315
+
316
+ def _resolve_type_alias(
317
+ annotation: TypeAliasType,
318
+ /,
319
+ module: str,
320
+ type_parameters: Mapping[str, Any],
321
+ self_annotation: AttributeAnnotation | None,
322
+ recursion_guard: MutableMapping[str, AttributeAnnotation],
323
+ ) -> AttributeAnnotation:
324
+ resolved_attribute = AttributeAnnotation(origin=MISSING)
325
+
326
+ if recursion_key := _recursion_key(annotation):
327
+ if recursive := recursion_guard.get(recursion_key):
328
+ return recursive
329
+
330
+ else:
331
+ recursion_guard[recursion_key] = resolved_attribute
332
+
333
+ resolved: AttributeAnnotation = resolve_attribute_annotation(
334
+ annotation.__value__,
335
+ module=annotation.__module__ or module,
336
+ type_parameters=type_parameters,
337
+ self_annotation=self_annotation,
338
+ recursion_guard=recursion_guard,
339
+ )
340
+
341
+ resolved_attribute.origin = resolved.origin
342
+ resolved_attribute.arguments = resolved.arguments
343
+ resolved_attribute.extra = {
344
+ **resolved.extra,
345
+ "TYPE_ALIAS": annotation.__name__,
346
+ }
347
+ resolved_attribute.required = resolved.required
348
+
349
+ return resolved_attribute
350
+
351
+
352
+ def _resolve_type_var(
353
+ annotation: TypeVar,
354
+ /,
355
+ module: str,
356
+ type_parameters: Mapping[str, Any],
357
+ self_annotation: AttributeAnnotation | None,
358
+ recursion_guard: MutableMapping[str, AttributeAnnotation],
359
+ ) -> AttributeAnnotation:
360
+ return resolve_attribute_annotation(
361
+ type_parameters.get(
362
+ annotation.__name__,
363
+ # use bound as default or Any otherwise
364
+ annotation.__bound__ or Any,
365
+ ),
366
+ module=module,
367
+ type_parameters=type_parameters,
368
+ self_annotation=self_annotation,
369
+ recursion_guard=recursion_guard,
370
+ )
371
+
372
+
373
+ def _resolve_type_union(
374
+ annotation: UnionType,
375
+ /,
376
+ module: str,
377
+ type_parameters: Mapping[str, Any],
378
+ self_annotation: AttributeAnnotation | None,
379
+ recursion_guard: MutableMapping[str, AttributeAnnotation],
380
+ ) -> AttributeAnnotation:
381
+ arguments: Sequence[AttributeAnnotation] = [
382
+ resolve_attribute_annotation(
383
+ argument,
384
+ type_parameters=type_parameters,
385
+ module=module,
386
+ self_annotation=self_annotation,
387
+ recursion_guard=recursion_guard,
388
+ )
389
+ for argument in get_args(annotation)
390
+ ]
391
+ return AttributeAnnotation(
392
+ origin=UnionType, # pyright: ignore[reportArgumentType]
393
+ arguments=arguments,
394
+ required=all(argument.required for argument in arguments),
395
+ )
396
+
397
+
398
+ def _resolve_callable(
399
+ annotation: Any,
400
+ /,
401
+ module: str,
402
+ type_parameters: Mapping[str, Any],
403
+ self_annotation: AttributeAnnotation | None,
404
+ recursion_guard: MutableMapping[str, AttributeAnnotation],
405
+ ) -> AttributeAnnotation:
406
+ return AttributeAnnotation(
407
+ origin=Callable,
408
+ arguments=[
409
+ resolve_attribute_annotation(
410
+ argument,
411
+ type_parameters=type_parameters,
412
+ module=module,
413
+ self_annotation=self_annotation,
414
+ recursion_guard=recursion_guard,
415
+ )
416
+ for argument in get_args(annotation)
417
+ ],
418
+ )
419
+
420
+
421
+ def _resolve_type_box(
422
+ annotation: Any,
423
+ /,
424
+ module: str,
425
+ type_parameters: Mapping[str, Any],
426
+ self_annotation: AttributeAnnotation | None,
427
+ recursion_guard: MutableMapping[str, AttributeAnnotation],
428
+ ) -> AttributeAnnotation:
429
+ return resolve_attribute_annotation(
430
+ get_args(annotation)[0],
431
+ type_parameters=type_parameters,
432
+ module=module,
433
+ self_annotation=self_annotation,
434
+ recursion_guard=recursion_guard,
435
+ )
436
+
437
+
438
+ def _resolve_type_not_required(
439
+ annotation: Any,
440
+ /,
441
+ module: str,
442
+ type_parameters: Mapping[str, Any],
443
+ self_annotation: AttributeAnnotation | None,
444
+ recursion_guard: MutableMapping[str, AttributeAnnotation],
445
+ ) -> AttributeAnnotation:
446
+ return resolve_attribute_annotation(
447
+ get_args(annotation)[0],
448
+ type_parameters=type_parameters,
449
+ module=module,
450
+ self_annotation=self_annotation,
451
+ recursion_guard=recursion_guard,
452
+ ).update_required(False)
453
+
454
+
455
+ def _resolve_type_optional(
456
+ annotation: Any,
457
+ /,
458
+ module: str,
459
+ type_parameters: Mapping[str, Any],
460
+ self_annotation: AttributeAnnotation | None,
461
+ recursion_guard: MutableMapping[str, AttributeAnnotation],
462
+ ) -> AttributeAnnotation:
463
+ return AttributeAnnotation(
464
+ origin=UnionType, # pyright: ignore[reportArgumentType]
465
+ arguments=[
466
+ resolve_attribute_annotation(
467
+ get_args(annotation)[0],
468
+ type_parameters=type_parameters,
469
+ module=module,
470
+ self_annotation=self_annotation,
471
+ recursion_guard=recursion_guard,
472
+ ),
473
+ AttributeAnnotation(origin=NoneType),
474
+ ],
475
+ )
476
+
477
+
478
+ def _resolve_type_typeddict(
479
+ annotation: Any,
480
+ /,
481
+ module: str,
482
+ type_parameters: Mapping[str, Any],
483
+ self_annotation: AttributeAnnotation | None,
484
+ recursion_guard: MutableMapping[str, AttributeAnnotation],
485
+ ) -> AttributeAnnotation:
486
+ resolved_attribute = AttributeAnnotation(origin=annotation)
487
+
488
+ if recursion_key := _recursion_key(annotation):
489
+ if recursive := recursion_guard.get(recursion_key):
490
+ return recursive
491
+
492
+ else:
493
+ recursion_guard[recursion_key] = resolved_attribute
494
+
495
+ resolved_attribute.arguments = [
496
+ resolve_attribute_annotation(
497
+ argument,
498
+ type_parameters=type_parameters,
499
+ module=module,
500
+ self_annotation=self_annotation,
501
+ recursion_guard=recursion_guard,
502
+ )
503
+ for argument in get_args(annotation)
504
+ ]
505
+
506
+ attributes: dict[str, AttributeAnnotation] = {}
507
+ for key, element in get_type_hints(
508
+ annotation,
509
+ localns={annotation.__name__: annotation},
510
+ ).items():
511
+ attributes[key] = resolve_attribute_annotation(
512
+ element,
513
+ type_parameters=type_parameters,
514
+ module=getattr(annotation, "__module__", module),
515
+ self_annotation=resolved_attribute,
516
+ recursion_guard=recursion_guard,
517
+ ).update_required(key in annotation.__required_keys__)
518
+ resolved_attribute.extra = {
519
+ "attributes": attributes,
520
+ "required": annotation.__required_keys__,
521
+ }
522
+ return resolved_attribute
523
+
524
+
525
+ def _resolve_type(
526
+ annotation: Any,
527
+ /,
528
+ module: str,
529
+ type_parameters: Mapping[str, Any],
530
+ self_annotation: AttributeAnnotation | None,
531
+ recursion_guard: MutableMapping[str, AttributeAnnotation],
532
+ ) -> AttributeAnnotation:
533
+ if recursion_key := _recursion_key(annotation):
534
+ if recursive := recursion_guard.get(recursion_key):
535
+ return recursive
536
+
537
+ # not updating recursion guard here - it might be a builtin type
538
+
539
+ return AttributeAnnotation(
540
+ origin=annotation,
541
+ arguments=[
542
+ resolve_attribute_annotation(
543
+ argument,
544
+ type_parameters=type_parameters,
545
+ module=module,
546
+ self_annotation=self_annotation,
547
+ recursion_guard=recursion_guard,
548
+ )
549
+ for argument in get_args(annotation)
550
+ ],
551
+ )
552
+
553
+
554
+ def resolve_attribute_annotation( # noqa: C901, PLR0911, PLR0912
555
+ annotation: Any,
556
+ /,
557
+ module: str,
558
+ type_parameters: Mapping[str, Any],
559
+ self_annotation: AttributeAnnotation | None,
560
+ recursion_guard: MutableMapping[str, AttributeAnnotation],
561
+ ) -> AttributeAnnotation:
562
+ match get_origin(annotation) or annotation:
563
+ case types.NoneType | None:
564
+ return _resolve_none(
565
+ annotation=annotation,
566
+ )
567
+
568
+ case haiway_types.Missing:
569
+ return _resolve_missing(
570
+ annotation=annotation,
571
+ )
572
+
573
+ case types.UnionType | typing.Union:
574
+ return _resolve_type_union(
575
+ annotation,
576
+ module=module,
577
+ type_parameters=type_parameters,
578
+ self_annotation=self_annotation,
579
+ recursion_guard=recursion_guard,
580
+ )
581
+
582
+ case typing.Literal:
583
+ return _resolve_literal(annotation)
584
+
585
+ case typeddict if is_typeddict(typeddict):
586
+ return _resolve_type_typeddict(
587
+ typeddict,
588
+ module=module,
589
+ type_parameters=type_parameters,
590
+ self_annotation=self_annotation,
591
+ recursion_guard=recursion_guard,
592
+ )
593
+
594
+ case typing.Callable: # pyright: ignore
595
+ return _resolve_callable(
596
+ annotation,
597
+ module=module,
598
+ type_parameters=type_parameters,
599
+ self_annotation=self_annotation,
600
+ recursion_guard=recursion_guard,
601
+ )
602
+
603
+ case typing.Annotated | typing.Final | typing.Required:
604
+ return _resolve_type_box(
605
+ annotation,
606
+ module=module,
607
+ type_parameters=type_parameters,
608
+ self_annotation=self_annotation,
609
+ recursion_guard=recursion_guard,
610
+ )
611
+
612
+ case typing.NotRequired:
613
+ return _resolve_type_not_required(
614
+ annotation,
615
+ module=module,
616
+ type_parameters=type_parameters,
617
+ self_annotation=self_annotation,
618
+ recursion_guard=recursion_guard,
619
+ )
620
+
621
+ case typing.Optional: # optional is a Union[Value, None]
622
+ return _resolve_type_optional(
623
+ annotation,
624
+ module=module,
625
+ type_parameters=type_parameters,
626
+ self_annotation=self_annotation,
627
+ recursion_guard=recursion_guard,
628
+ )
629
+
630
+ case typing.Self: # pyright: ignore
631
+ if self_annotation:
632
+ return self_annotation
633
+
634
+ else:
635
+ raise RuntimeError(f"Unresolved Self annotation: {annotation}")
636
+
637
+ case _:
638
+ match annotation:
639
+ case str() | ForwardRef():
640
+ return _resolve_forward_ref(
641
+ annotation,
642
+ module=module,
643
+ type_parameters=type_parameters,
644
+ self_annotation=self_annotation,
645
+ recursion_guard=recursion_guard,
646
+ )
647
+
648
+ case GenericAlias():
649
+ return _resolve_generic_alias(
650
+ annotation,
651
+ module=module,
652
+ type_parameters=type_parameters,
653
+ self_annotation=self_annotation,
654
+ recursion_guard=recursion_guard,
655
+ )
656
+
657
+ case _GenericAlias():
658
+ return _resolve_special_generic_alias(
659
+ annotation,
660
+ module=module,
661
+ type_parameters=type_parameters,
662
+ self_annotation=self_annotation,
663
+ recursion_guard=recursion_guard,
664
+ )
665
+
666
+ case TypeAliasType():
667
+ return _resolve_type_alias(
668
+ annotation,
669
+ module=module,
670
+ type_parameters=type_parameters,
671
+ self_annotation=self_annotation,
672
+ recursion_guard=recursion_guard,
673
+ )
674
+
675
+ case TypeVar():
676
+ return _resolve_type_var(
677
+ annotation,
678
+ module=module,
679
+ type_parameters=type_parameters,
680
+ self_annotation=self_annotation,
681
+ recursion_guard=recursion_guard,
682
+ )
683
+
684
+ case ParamSpec():
685
+ raise NotImplementedError(f"Unresolved ParamSpec annotation: {annotation}")
686
+
687
+ case TypeVarTuple():
688
+ raise NotImplementedError(f"Unresolved TypeVarTuple annotation: {annotation}")
689
+
690
+ case _: # finally use whatever there was
691
+ return _resolve_type(
692
+ annotation,
693
+ module=module,
694
+ type_parameters=type_parameters,
695
+ self_annotation=self_annotation,
696
+ recursion_guard=recursion_guard,
697
+ )
698
+
699
+
700
+ @overload
701
+ def _recursion_key(
702
+ annotation: Any,
703
+ /,
704
+ ) -> str | None: ...
705
+
706
+
707
+ @overload
708
+ def _recursion_key(
709
+ annotation: Any,
710
+ /,
711
+ default: str,
712
+ ) -> str: ...
713
+
714
+
715
+ def _recursion_key(
716
+ annotation: Any,
717
+ /,
718
+ default: str | None = None,
719
+ ) -> str | None:
720
+ args_suffix: str
721
+ if arguments := get_args(annotation):
722
+ arguments_string: str = ", ".join(
723
+ _recursion_key(
724
+ argument,
725
+ default="?",
726
+ )
727
+ for argument in arguments
728
+ )
729
+ args_suffix = f"[{arguments_string}]"
730
+
731
+ else:
732
+ args_suffix = ""
733
+
734
+ if qualname := getattr(annotation, "__qualname__", None):
735
+ return qualname + args_suffix
736
+
737
+ module_prefix: str
738
+ if module := getattr(annotation, "__module__", None):
739
+ module_prefix = module + "."
740
+
741
+ else:
742
+ module_prefix = ""
743
+
744
+ if name := getattr(annotation, "__name__", None):
745
+ return module_prefix + name + args_suffix
746
+
747
+ return default