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