structtype 0.3.0__cp315-cp315-win_amd64.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.
structtype/_inspect.py ADDED
@@ -0,0 +1,1183 @@
1
+ import datetime
2
+ import decimal
3
+ import enum
4
+ import sys
5
+ import uuid
6
+ from collections.abc import Iterable
7
+ from types import UnionType as _types_UnionType
8
+ from typing import Any, Final, Literal, TypeVar, Union
9
+
10
+ try:
11
+ from typing import TypeAliasType as _TypeAliasType # type: ignore
12
+ except Exception:
13
+ _TypeAliasType = type("TypeAliasType", (), {}) # type: ignore
14
+
15
+ import structtype
16
+ from structtype import NODEFAULT, UNSET
17
+ from structtype import UnsetType as _UnsetType
18
+
19
+ from ._core import ( # type: ignore
20
+ Factory as _Factory,
21
+ )
22
+ from ._core import (
23
+ Struct,
24
+ StructMeta,
25
+ _dump,
26
+ )
27
+ from ._utils import ( # type: ignore
28
+ _CONCRETE_TYPES,
29
+ _AnnotatedAlias,
30
+ )
31
+ from ._utils import (
32
+ get_class_annotations as _get_class_annotations,
33
+ )
34
+ from ._utils import (
35
+ get_dataclass_info as _get_dataclass_info,
36
+ )
37
+ from ._utils import (
38
+ get_pydantic_info as _get_pydantic_info,
39
+ )
40
+ from ._utils import (
41
+ get_typeddict_info as _get_typeddict_info,
42
+ )
43
+
44
+ __all__ = (
45
+ "AnyType",
46
+ "BoolType",
47
+ "ByteArrayType",
48
+ "BytesType",
49
+ "CollectionType",
50
+ "CustomType",
51
+ "DataclassType",
52
+ "DateTimeType",
53
+ "DateType",
54
+ "DecimalType",
55
+ "DictType",
56
+ "EnumType",
57
+ "FieldInfo",
58
+ "FieldNode",
59
+ "FloatType",
60
+ "FrozenDictType",
61
+ "FrozenSetType",
62
+ "IntType",
63
+ "ListType",
64
+ "LiteralType",
65
+ "MemoryViewType",
66
+ "Metadata",
67
+ "NamedTupleType",
68
+ "NoneType",
69
+ "PydanticType",
70
+ "RawType",
71
+ "SetType",
72
+ "StrType",
73
+ "StructType",
74
+ "TimeDeltaType",
75
+ "TimeType",
76
+ "TupleType",
77
+ "Type",
78
+ "TypedDictType",
79
+ "UUIDType",
80
+ "UnionType",
81
+ "VarTupleType",
82
+ )
83
+
84
+ _PY315_PLUS = sys.version_info >= (3, 15)
85
+
86
+
87
+ def __dir__():
88
+ return __all__
89
+
90
+
91
+ class Type(structtype.Struct):
92
+ """The base Type."""
93
+
94
+
95
+ class Metadata(Type):
96
+ """A type wrapping a subtype with additional metadata.
97
+
98
+ Parameters
99
+ ----------
100
+ type: Type
101
+ The subtype.
102
+ json_schema_extra: dict, optional
103
+ A dict of extra fields to set for the subtype when generating a
104
+ json-schema.
105
+ """
106
+
107
+ type: Type
108
+ json_schema_extra: dict | None = None
109
+
110
+
111
+ class AnyType(Type):
112
+ """A type corresponding to `typing.Any`."""
113
+
114
+
115
+ class NoneType(Type):
116
+ """A type corresponding to `None`."""
117
+
118
+
119
+ class BoolType(Type):
120
+ """A type corresponding to `bool`."""
121
+
122
+
123
+ class IntType(Type):
124
+ """A type corresponding to `int`.
125
+
126
+ Parameters
127
+ ----------
128
+ gt: int, optional
129
+ If set, an instance of this type must be greater than ``gt``.
130
+ ge: int, optional
131
+ If set, an instance of this type must be greater than or equal to ``ge``.
132
+ lt: int, optional
133
+ If set, an instance of this type must be less than to ``lt``.
134
+ le: int, optional
135
+ If set, an instance of this type must be less than or equal to ``le``.
136
+ multiple_of: int, optional
137
+ If set, an instance of this type must be a multiple of ``multiple_of``.
138
+ """
139
+
140
+ gt: int | None = None
141
+ ge: int | None = None
142
+ lt: int | None = None
143
+ le: int | None = None
144
+ multiple_of: int | None = None
145
+
146
+
147
+ class FloatType(Type):
148
+ """A type corresponding to `float`.
149
+
150
+ Parameters
151
+ ----------
152
+ gt: float, optional
153
+ If set, an instance of this type must be greater than ``gt``.
154
+ ge: float, optional
155
+ If set, an instance of this type must be greater than or equal to ``ge``.
156
+ lt: float, optional
157
+ If set, an instance of this type must be less than to ``lt``.
158
+ le: float, optional
159
+ If set, an instance of this type must be less than or equal to ``le``.
160
+ multiple_of: float, optional
161
+ If set, an instance of this type must be a multiple of ``multiple_of``.
162
+ """
163
+
164
+ gt: float | None = None
165
+ ge: float | None = None
166
+ lt: float | None = None
167
+ le: float | None = None
168
+ multiple_of: float | None = None
169
+
170
+
171
+ class StrType(Type):
172
+ """A type corresponding to `str`.
173
+
174
+ Parameters
175
+ ----------
176
+ min_length: int, optional
177
+ If set, an instance of this type must have length greater than or equal
178
+ to ``min_length``.
179
+ max_length: int, optional
180
+ If set, an instance of this type must have length less than or equal
181
+ to ``max_length``.
182
+ pattern: str, optional
183
+ If set, an instance of this type must match against this regex pattern.
184
+ Note that the pattern is treated as **unanchored**.
185
+ """
186
+
187
+ min_length: int | None = None
188
+ max_length: int | None = None
189
+ pattern: str | None = None
190
+
191
+
192
+ class BytesType(Type):
193
+ """A type corresponding to `bytes`.
194
+
195
+ Parameters
196
+ ----------
197
+ min_length: int, optional
198
+ If set, an instance of this type must have length greater than or equal
199
+ to ``min_length``.
200
+ max_length: int, optional
201
+ If set, an instance of this type must have length less than or equal
202
+ to ``max_length``.
203
+ """
204
+
205
+ min_length: int | None = None
206
+ max_length: int | None = None
207
+
208
+
209
+ class ByteArrayType(Type):
210
+ """A type corresponding to `bytearray`.
211
+
212
+ Parameters
213
+ ----------
214
+ min_length: int, optional
215
+ If set, an instance of this type must have length greater than or equal
216
+ to ``min_length``.
217
+ max_length: int, optional
218
+ If set, an instance of this type must have length less than or equal
219
+ to ``max_length``.
220
+ """
221
+
222
+ min_length: int | None = None
223
+ max_length: int | None = None
224
+
225
+
226
+ class MemoryViewType(Type):
227
+ """A type corresponding to `memoryview`.
228
+
229
+ Parameters
230
+ ----------
231
+ min_length: int, optional
232
+ If set, an instance of this type must have length greater than or equal
233
+ to ``min_length``.
234
+ max_length: int, optional
235
+ If set, an instance of this type must have length less than or equal
236
+ to ``max_length``.
237
+ """
238
+
239
+ min_length: int | None = None
240
+ max_length: int | None = None
241
+
242
+
243
+ class DateTimeType(Type):
244
+ """A type corresponding to `datetime.datetime`.
245
+
246
+ Parameters
247
+ ----------
248
+ tz: bool
249
+ The timezone-requirements for an instance of this type. ``True``
250
+ indicates a timezone-aware value is required, ``False`` indicates a
251
+ timezone-naive value is required. The default is ``None``, which
252
+ accepts either timezone-aware or timezone-naive values.
253
+ """
254
+
255
+ tz: bool | None = None
256
+
257
+
258
+ class TimeType(Type):
259
+ """A type corresponding to `datetime.time`.
260
+
261
+ Parameters
262
+ ----------
263
+ tz: bool
264
+ The timezone-requirements for an instance of this type. ``True``
265
+ indicates a timezone-aware value is required, ``False`` indicates a
266
+ timezone-aware value is required. The default is ``None``, which
267
+ accepts either timezone-aware or timezone-naive values.
268
+ """
269
+
270
+ tz: bool | None = None
271
+
272
+
273
+ class DateType(Type):
274
+ """A type corresponding to `datetime.date`."""
275
+
276
+
277
+ class TimeDeltaType(Type):
278
+ """A type corresponding to `datetime.timedelta`."""
279
+
280
+
281
+ class UUIDType(Type):
282
+ """A type corresponding to `uuid.UUID`."""
283
+
284
+
285
+ class DecimalType(Type):
286
+ """A type corresponding to `decimal.Decimal`."""
287
+
288
+
289
+ class RawType(Type):
290
+ """A type corresponding to `structtype.Raw`."""
291
+
292
+
293
+ class EnumType(Type):
294
+ """A type corresponding to an `enum.Enum` type.
295
+
296
+ Parameters
297
+ ----------
298
+ cls: type
299
+ The corresponding `enum.Enum` type.
300
+ """
301
+
302
+ cls: type[enum.Enum]
303
+
304
+
305
+ class LiteralType(Type):
306
+ """A type corresponding to a `typing.Literal` type.
307
+
308
+ Parameters
309
+ ----------
310
+ values: tuple
311
+ A tuple of possible values for this literal instance. Only `bool`,
312
+ `str`, `int`, or `None` literals are supported, and a single literal
313
+ may mix these types (e.g. ``Literal[1, None]``).
314
+ """
315
+
316
+ values: tuple[bool | str | int | None, ...]
317
+
318
+
319
+ class CustomType(Type):
320
+ """A custom type.
321
+
322
+ Parameters
323
+ ----------
324
+ cls: type
325
+ The corresponding custom type.
326
+ """
327
+
328
+ cls: type
329
+
330
+
331
+ class UnionType(Type):
332
+ """A union type.
333
+
334
+ Parameters
335
+ ----------
336
+ types: tuple[type, ...]
337
+ A tuple of possible types for this union.
338
+ """
339
+
340
+ types: tuple[Type, ...]
341
+
342
+ @property
343
+ def includes_none(self) -> bool:
344
+ """A helper for checking whether ``None`` is included in this union."""
345
+ return any(isinstance(t, NoneType) for t in self.types)
346
+
347
+
348
+ class CollectionType(Type):
349
+ """A collection type.
350
+
351
+ This is the base type shared by collection types like `ListType`,
352
+ `SetType`, etc.
353
+
354
+ Parameters
355
+ ----------
356
+ item_type: Type
357
+ The item type.
358
+ min_length: int, optional
359
+ If set, an instance of this type must have length greater than or equal
360
+ to ``min_length``.
361
+ max_length: int, optional
362
+ If set, an instance of this type must have length less than or equal
363
+ to ``max_length``.
364
+ """
365
+
366
+ item_type: Type
367
+ min_length: int | None = None
368
+ max_length: int | None = None
369
+
370
+
371
+ class ListType(CollectionType):
372
+ """A type corresponding to a `list`.
373
+
374
+ Parameters
375
+ ----------
376
+ item_type: Type
377
+ The item type.
378
+ min_length: int, optional
379
+ If set, an instance of this type must have length greater than or equal
380
+ to ``min_length``.
381
+ max_length: int, optional
382
+ If set, an instance of this type must have length less than or equal
383
+ to ``max_length``.
384
+ """
385
+
386
+
387
+ class VarTupleType(CollectionType):
388
+ """A type corresponding to a variadic `tuple`.
389
+
390
+ Parameters
391
+ ----------
392
+ item_type: Type
393
+ The item type.
394
+ min_length: int, optional
395
+ If set, an instance of this type must have length greater than or equal
396
+ to ``min_length``.
397
+ max_length: int, optional
398
+ If set, an instance of this type must have length less than or equal
399
+ to ``max_length``.
400
+ """
401
+
402
+
403
+ class SetType(CollectionType):
404
+ """A type corresponding to a `set`.
405
+
406
+ Parameters
407
+ ----------
408
+ item_type: Type
409
+ The item type.
410
+ min_length: int, optional
411
+ If set, an instance of this type must have length greater than or equal
412
+ to ``min_length``.
413
+ max_length: int, optional
414
+ If set, an instance of this type must have length less than or equal
415
+ to ``max_length``.
416
+ """
417
+
418
+
419
+ class FrozenSetType(CollectionType):
420
+ """A type corresponding to a `frozenset`.
421
+
422
+ Parameters
423
+ ----------
424
+ item_type: Type
425
+ The item type.
426
+ min_length: int, optional
427
+ If set, an instance of this type must have length greater than or equal
428
+ to ``min_length``.
429
+ max_length: int, optional
430
+ If set, an instance of this type must have length less than or equal
431
+ to ``max_length``.
432
+ """
433
+
434
+
435
+ class TupleType(Type):
436
+ """A type corresponding to `tuple`.
437
+
438
+ Parameters
439
+ ----------
440
+ item_types: tuple[Type, ...]
441
+ A tuple of types for each element in the tuple.
442
+ """
443
+
444
+ item_types: tuple[Type, ...]
445
+
446
+
447
+ class DictType(Type):
448
+ """A type corresponding to `dict`.
449
+
450
+ Parameters
451
+ ----------
452
+ key_type: Type
453
+ The key type.
454
+ value_type: Type
455
+ The value type.
456
+ min_length: int, optional
457
+ If set, an instance of this type must have length greater than or equal
458
+ to ``min_length``.
459
+ max_length: int, optional
460
+ If set, an instance of this type must have length less than or equal
461
+ to ``max_length``.
462
+ """
463
+
464
+ key_type: Type
465
+ value_type: Type
466
+ min_length: int | None = None
467
+ max_length: int | None = None
468
+
469
+
470
+ class FrozenDictType(Type):
471
+ """A type corresponding to `frozendict`.
472
+
473
+ Can only be emitted on Python 3.15+.
474
+
475
+ Parameters
476
+ ----------
477
+ key_type: Type
478
+ The key type.
479
+ value_type: Type
480
+ The value type.
481
+ min_length: int, optional
482
+ If set, an instance of this type must have length greater than or equal
483
+ to ``min_length``.
484
+ max_length: int, optional
485
+ If set, an instance of this type must have length less than or equal
486
+ to ``max_length``.
487
+ """
488
+
489
+ key_type: Type
490
+ value_type: Type
491
+ min_length: Union[int, None] = None
492
+ max_length: Union[int, None] = None
493
+
494
+
495
+ class FieldNode(structtype.Struct):
496
+ """A record describing a field in an object-like type.
497
+
498
+ Parameters
499
+ ----------
500
+ name: str
501
+ The field name as seen by Python code (e.g. ``field_one``).
502
+ encode_name: str
503
+ The name used when encoding/decoding the field. This may differ if
504
+ the field is renamed (e.g. ``fieldOne``).
505
+ type: Type
506
+ The field type.
507
+ required: bool, optional
508
+ Whether the field is required. Note that if `required` is False doesn't
509
+ necessarily mean that `default` or `default_factory` will be set -
510
+ optional fields may exist with no default value.
511
+ default: Any, optional
512
+ A default value for the field. Will be `NODEFAULT` if no default value
513
+ is set.
514
+ default_factory: Any, optional
515
+ A callable that creates a default value for the field. Will be
516
+ `NODEFAULT` if no ``default_factory`` is set.
517
+ """
518
+
519
+ _FIELD_UNSET = object()
520
+
521
+ name: str
522
+ encode_name: str
523
+ type: Type
524
+ required: bool = True
525
+ default: Any = _FIELD_UNSET
526
+ default_factory: Any = _FIELD_UNSET
527
+
528
+ def __post_init__(self):
529
+ if self.default is self._FIELD_UNSET:
530
+ self.default = NODEFAULT
531
+ if self.default_factory is self._FIELD_UNSET:
532
+ self.default_factory = NODEFAULT
533
+
534
+
535
+ class TypedDictType(Type):
536
+ """A type corresponding to a `typing.TypedDict` type.
537
+
538
+ Parameters
539
+ ----------
540
+ cls: type
541
+ The corresponding TypedDict type.
542
+ fields: tuple[FieldNode, ...]
543
+ A tuple of fields in the TypedDict.
544
+ """
545
+
546
+ cls: type
547
+ fields: tuple[FieldNode, ...]
548
+
549
+
550
+ class NamedTupleType(Type):
551
+ """A type corresponding to a `typing.NamedTuple` type.
552
+
553
+ Parameters
554
+ ----------
555
+ cls: type
556
+ The corresponding NamedTuple type.
557
+ fields: tuple[FieldNode, ...]
558
+ A tuple of fields in the NamedTuple.
559
+ """
560
+
561
+ cls: type
562
+ fields: tuple[FieldNode, ...]
563
+
564
+
565
+ class DataclassType(Type):
566
+ """A type corresponding to a `dataclasses` or `attrs` type.
567
+
568
+ Parameters
569
+ ----------
570
+ cls: type
571
+ The corresponding dataclass type.
572
+ fields: tuple[FieldNode, ...]
573
+ A tuple of fields in the dataclass.
574
+ """
575
+
576
+ cls: type
577
+ fields: tuple[FieldNode, ...]
578
+
579
+
580
+ class PydanticType(Type):
581
+ """A type corresponding to a Pydantic v2 BaseModel type.
582
+
583
+ Parameters
584
+ ----------
585
+ cls: type
586
+ The corresponding Pydantic model type.
587
+ fields: tuple[FieldNode, ...]
588
+ A tuple of fields in the Pydantic model.
589
+ """
590
+
591
+ cls: type
592
+ fields: tuple[FieldNode, ...]
593
+
594
+
595
+ class StructType(Type):
596
+ """A type corresponding to a `structtype.Struct` type.
597
+
598
+ Parameters
599
+ ----------
600
+ cls: type
601
+ The corresponding Struct type.
602
+ fields: tuple[FieldNode, ...]
603
+ A tuple of fields in the Struct.
604
+ tag_field: str or None, optional
605
+ If set, the field name used for the tag in a tagged union.
606
+ tag: str, int, or None, optional
607
+ If set, the value used for the tag in a tagged union.
608
+ array_like: bool, optional
609
+ Whether the struct is encoded as an array rather than an object.
610
+ forbid_unknown_fields: bool, optional
611
+ If ``False`` (the default) unknown fields are ignored when decoding. If
612
+ ``True`` any unknown fields will result in an error.
613
+ """
614
+
615
+ cls: type[structtype.Struct]
616
+ fields: tuple[FieldNode, ...]
617
+ tag_field: str | None = None
618
+ tag: str | int | None = None
619
+ array_like: bool = False
620
+ forbid_unknown_fields: bool = False
621
+
622
+
623
+ class FieldInfo(structtype.Struct):
624
+ """A record describing a field in a struct."""
625
+
626
+ name: str
627
+ encode_name: str
628
+ type: Any
629
+ default: Any = UNSET
630
+ default_factory: Any = UNSET
631
+
632
+ def __post_init__(self):
633
+ if self.default is UNSET:
634
+ self.default = NODEFAULT
635
+ if self.default_factory is UNSET:
636
+ self.default_factory = NODEFAULT
637
+
638
+ @property
639
+ def required(self) -> bool:
640
+ return self.default is NODEFAULT and self.default_factory is NODEFAULT
641
+
642
+
643
+ def fields(type_or_instance: Struct | type[Struct]) -> tuple[FieldInfo, ...]:
644
+ """Get introspection information about a struct's fields.
645
+
646
+ Returns a tuple of :class:`FieldInfo` objects describing each field in
647
+ declaration order.
648
+
649
+ Parameters
650
+ ----------
651
+ type_or_instance : Struct or type[Struct]
652
+ A struct type or struct instance.
653
+
654
+ Returns
655
+ -------
656
+ tuple[FieldInfo, ...]
657
+ A tuple of FieldInfo objects, one per field in declaration order.
658
+
659
+ Examples
660
+ --------
661
+ >>> from structtype import Struct, fields
662
+
663
+ >>> class Point(Struct):
664
+ ... x: float
665
+ ... y: float
666
+
667
+ >>> fields(Point)
668
+ (FieldInfo(name='x', encode_name='x', type=<class 'float'>, required=True),
669
+ FieldInfo(name='y', encode_name='y', type=<class 'float'>, required=True))
670
+ """
671
+ obj = type_or_instance
672
+ if isinstance(obj, StructMeta):
673
+ annotated_cls = cls = obj
674
+ elif isinstance(type(obj), StructMeta):
675
+ annotated_cls = cls = type(obj)
676
+ else:
677
+ annotated_cls = obj
678
+ cls = getattr(obj, "__origin__", obj)
679
+ if not isinstance(cls, StructMeta):
680
+ raise TypeError("Must be called with a struct type or instance")
681
+
682
+ hints = _get_class_annotations(annotated_cls)
683
+ npos = len(cls.__struct_fields__) - len(cls.__struct_defaults__)
684
+ fields_list = []
685
+ for name, encode_name, default_obj in zip(
686
+ cls.__struct_fields__,
687
+ cls.__struct_encode_fields__,
688
+ (NODEFAULT,) * npos + cls.__struct_defaults__,
689
+ ):
690
+ default = default_factory = NODEFAULT
691
+ if isinstance(default_obj, _Factory):
692
+ default_factory = default_obj.factory
693
+ elif default_obj is not NODEFAULT:
694
+ default = default_obj
695
+
696
+ field_instance = FieldInfo(
697
+ name=name,
698
+ encode_name=encode_name,
699
+ type=hints[name],
700
+ default=default,
701
+ default_factory=default_factory,
702
+ )
703
+ fields_list.append(field_instance)
704
+
705
+ return tuple(fields_list)
706
+
707
+
708
+ def multi_type_info(types: Iterable[Any]) -> tuple[Type, ...]:
709
+ """Get information about multiple structtype-compatible types.
710
+
711
+ Parameters
712
+ ----------
713
+ types: an iterable of types
714
+ The types to get info about.
715
+
716
+ Returns
717
+ -------
718
+ tuple[Type, ...]
719
+
720
+ Examples
721
+ --------
722
+ >>> structtype._inspect.multi_type_info([int, float, list[str]]) # doctest: +NORMALIZE_WHITESPACE
723
+ (IntType(gt=None, ge=None, lt=None, le=None, multiple_of=None),
724
+ FloatType(gt=None, ge=None, lt=None, le=None, multiple_of=None),
725
+ ListType(item_type=StrType(min_length=None, max_length=None, pattern=None),
726
+ min_length=None, max_length=None))
727
+ """
728
+ return _Translator(types).run()
729
+
730
+
731
+ def type_info(type: Any) -> Type:
732
+ """Get information about a structtype-compatible type.
733
+
734
+ Note that if you need to inspect multiple types it's more efficient to call
735
+ `multi_type_info` once with a sequence of types than calling `type_info`
736
+ multiple times.
737
+
738
+ Parameters
739
+ ----------
740
+ type: type
741
+ The type to get info about.
742
+
743
+ Returns
744
+ -------
745
+ Type
746
+
747
+ Examples
748
+ --------
749
+ >>> structtype._inspect.type_info(bool)
750
+ BoolType()
751
+
752
+ >>> structtype._inspect.type_info(int)
753
+ IntType(gt=None, ge=None, lt=None, le=None, multiple_of=None)
754
+
755
+ >>> structtype._inspect.type_info(list[int]) # doctest: +NORMALIZE_WHITESPACE
756
+ ListType(item_type=IntType(gt=None, ge=None, lt=None, le=None, multiple_of=None),
757
+ min_length=None, max_length=None)
758
+ """
759
+ return multi_type_info([type])[0]
760
+
761
+
762
+ # Implementation details
763
+ def _origin_args_metadata(t):
764
+ # Strip wrappers (Annotated, NewType, Final) until we hit a concrete type
765
+ metadata = []
766
+ while True:
767
+ try:
768
+ origin = _CONCRETE_TYPES.get(t)
769
+ except TypeError:
770
+ # t is not hashable
771
+ origin = None
772
+
773
+ if origin is not None:
774
+ args = None
775
+ break
776
+
777
+ origin = getattr(t, "__origin__", None)
778
+ if origin is not None:
779
+ if type(t) is _AnnotatedAlias:
780
+ metadata.extend(
781
+ m for m in t.__metadata__ if type(m) is structtype.Field
782
+ )
783
+ t = origin
784
+ elif origin == Final:
785
+ t = t.__args__[0]
786
+ elif type(origin) is _TypeAliasType:
787
+ t = origin.__value__[t.__args__]
788
+ else:
789
+ args = getattr(t, "__args__", None)
790
+ origin = _CONCRETE_TYPES.get(origin, origin)
791
+ break
792
+ else:
793
+ supertype = getattr(t, "__supertype__", None)
794
+ if supertype is not None:
795
+ t = supertype
796
+ elif type(t) is _TypeAliasType:
797
+ t = t.__value__
798
+ else:
799
+ origin = t
800
+ args = None
801
+ break
802
+
803
+ if type(origin) is _types_UnionType:
804
+ args = origin.__args__
805
+ origin = Union
806
+ return origin, args, tuple(metadata)
807
+
808
+
809
+ def _sort_literal_args(args):
810
+ # `Literal` may mix value types (e.g. `Literal[1, None]`), which a plain
811
+ # `sorted` can't order since Python 3 forbids comparing across types. Sort
812
+ # by type name first so members of the same type stay ordered as before,
813
+ # while mixed-type literals are grouped deterministically instead of
814
+ # crashing.
815
+ try:
816
+ return tuple(sorted(args, key=lambda x: (type(x).__name__, x)))
817
+ except TypeError:
818
+ return tuple(args)
819
+
820
+
821
+ def _is_enum(t):
822
+ return type(t) is enum.EnumMeta
823
+
824
+
825
+ def _is_dataclass(t):
826
+ return hasattr(t, "__dataclass_fields__")
827
+
828
+
829
+ def _is_attrs(t):
830
+ return hasattr(t, "__attrs_attrs__")
831
+
832
+
833
+ def _is_pydantic(t):
834
+ return hasattr(t, "model_fields") and hasattr(t, "model_validate")
835
+
836
+
837
+ def _is_typeddict(t):
838
+ try:
839
+ return issubclass(t, dict) and hasattr(t, "__total__")
840
+ except TypeError:
841
+ return False
842
+
843
+
844
+ def _is_namedtuple(t):
845
+ try:
846
+ return issubclass(t, tuple) and hasattr(t, "_fields")
847
+ except TypeError:
848
+ return False
849
+
850
+
851
+ def _merge_json(a, b):
852
+ if b:
853
+ a = a.copy()
854
+ for key, b_val in b.items():
855
+ if key in a:
856
+ a_val = a[key]
857
+ if isinstance(a_val, dict) and isinstance(b_val, dict):
858
+ a[key] = _merge_json(a_val, b_val)
859
+ elif isinstance(a_val, (list, tuple)) and isinstance(
860
+ b_val, (list, tuple)
861
+ ):
862
+ a[key] = list(a_val) + list(b_val)
863
+ else:
864
+ a[key] = b_val
865
+ else:
866
+ a[key] = b_val
867
+ return a
868
+
869
+
870
+ class _Translator:
871
+ def __init__(self, types):
872
+ self.types = tuple(types)
873
+ self.type_hints = {}
874
+ self.cache = {}
875
+
876
+ def _get_class_annotations(self, t):
877
+ """A cached version of `get_class_annotations`"""
878
+ try:
879
+ return self.type_hints[t]
880
+ except KeyError:
881
+ out = self.type_hints[t] = _get_class_annotations(t)
882
+ return out
883
+
884
+ def run(self):
885
+ # First construct a decoder to validate the types are valid
886
+ from ._core import JSONDecoder
887
+
888
+ JSONDecoder(tuple[self.types])
889
+ return tuple(self.translate(t) for t in self.types)
890
+
891
+ def translate(self, typ):
892
+ t, args, metadata = _origin_args_metadata(typ)
893
+
894
+ # Extract and merge components of any `Field` annotations
895
+ constrs = {}
896
+ extra_json_schema = {}
897
+ for meta in metadata:
898
+ for attr in (
899
+ "ge",
900
+ "gt",
901
+ "le",
902
+ "lt",
903
+ "multiple_of",
904
+ "pattern",
905
+ "min_length",
906
+ "max_length",
907
+ "tz",
908
+ ):
909
+ if (val := getattr(meta, attr)) is not None:
910
+ constrs[attr] = val
911
+ for attr in ("title", "description", "examples"):
912
+ if (val := getattr(meta, attr)) is not None:
913
+ extra_json_schema[attr] = val
914
+ if meta.json_schema_extra is not None:
915
+ extra_json_schema = _merge_json(
916
+ extra_json_schema,
917
+ _dump(meta.json_schema_extra, str_keys=True),
918
+ )
919
+
920
+ out = self._translate_inner(t, args, **constrs)
921
+ if extra_json_schema:
922
+ # If extra metadata is present, wrap the output type in a Metadata
923
+ # wrapper object
924
+ return Metadata(out, json_schema_extra=extra_json_schema or None)
925
+ return out
926
+
927
+ def _translate_inner(
928
+ self,
929
+ t,
930
+ args,
931
+ ge=None,
932
+ gt=None,
933
+ le=None,
934
+ lt=None,
935
+ multiple_of=None,
936
+ pattern=None,
937
+ min_length=None,
938
+ max_length=None,
939
+ tz=None,
940
+ ):
941
+ if t is Any:
942
+ return AnyType()
943
+ elif isinstance(t, TypeVar):
944
+ if t.__bound__ is not None:
945
+ return self.translate(t.__bound__)
946
+ return AnyType()
947
+ elif t is None or t is type(None):
948
+ return NoneType()
949
+ elif t is bool:
950
+ return BoolType()
951
+ elif t is int:
952
+ return IntType(ge=ge, gt=gt, le=le, lt=lt, multiple_of=multiple_of)
953
+ elif t is float:
954
+ return FloatType(ge=ge, gt=gt, le=le, lt=lt, multiple_of=multiple_of)
955
+ elif t is str:
956
+ return StrType(
957
+ min_length=min_length, max_length=max_length, pattern=pattern
958
+ )
959
+ elif t is bytes:
960
+ return BytesType(min_length=min_length, max_length=max_length)
961
+ elif t is bytearray:
962
+ return ByteArrayType(min_length=min_length, max_length=max_length)
963
+ elif t is memoryview:
964
+ return MemoryViewType(min_length=min_length, max_length=max_length)
965
+ elif t is datetime.datetime:
966
+ return DateTimeType(tz=tz)
967
+ elif t is datetime.time:
968
+ return TimeType(tz=tz)
969
+ elif t is datetime.date:
970
+ return DateType()
971
+ elif t is datetime.timedelta:
972
+ return TimeDeltaType()
973
+ elif t is uuid.UUID:
974
+ return UUIDType()
975
+ elif t is decimal.Decimal:
976
+ return DecimalType()
977
+ elif t is structtype.Raw:
978
+ return RawType()
979
+ elif False: # Ext type removed
980
+ return ExtType()
981
+ elif t is list:
982
+ return ListType(
983
+ self.translate(args[0]) if args else AnyType(),
984
+ min_length=min_length,
985
+ max_length=max_length,
986
+ )
987
+ elif t is set:
988
+ return SetType(
989
+ self.translate(args[0]) if args else AnyType(),
990
+ min_length=min_length,
991
+ max_length=max_length,
992
+ )
993
+ elif t is frozenset:
994
+ return FrozenSetType(
995
+ self.translate(args[0]) if args else AnyType(),
996
+ min_length=min_length,
997
+ max_length=max_length,
998
+ )
999
+ elif t is tuple:
1000
+ # Handle an annoying compatibility issue:
1001
+ # - Tuple[()] has args == ((),)
1002
+ # - tuple[()] has args == ()
1003
+ if args == ((),):
1004
+ args = ()
1005
+ if args is None:
1006
+ return VarTupleType(
1007
+ AnyType(), min_length=min_length, max_length=max_length
1008
+ )
1009
+ elif len(args) == 2 and args[-1] is ...:
1010
+ return VarTupleType(
1011
+ self.translate(args[0]),
1012
+ min_length=min_length,
1013
+ max_length=max_length,
1014
+ )
1015
+ else:
1016
+ return TupleType(tuple(self.translate(a) for a in args))
1017
+ elif t is dict:
1018
+ return DictType(
1019
+ self.translate(args[0]) if args else AnyType(),
1020
+ self.translate(args[1]) if args else AnyType(),
1021
+ min_length=min_length,
1022
+ max_length=max_length,
1023
+ )
1024
+ elif _PY315_PLUS and t is frozendict: # noqa: F821
1025
+ return FrozenDictType(
1026
+ self.translate(args[0]) if args else AnyType(),
1027
+ self.translate(args[1]) if args else AnyType(),
1028
+ min_length=min_length,
1029
+ max_length=max_length,
1030
+ )
1031
+ elif t is Union:
1032
+ args = tuple(self.translate(a) for a in args if a is not _UnsetType)
1033
+ return args[0] if len(args) == 1 else UnionType(args)
1034
+ elif t is Literal:
1035
+ return LiteralType(_sort_literal_args(args))
1036
+ elif _is_enum(t):
1037
+ return EnumType(t)
1038
+ elif isinstance(t, StructMeta):
1039
+ cls = t[args] if args else t
1040
+ if cls in self.cache:
1041
+ return self.cache[cls]
1042
+ config = t.__struct_config__
1043
+ self.cache[cls] = out = StructType(
1044
+ cls,
1045
+ (),
1046
+ tag_field=config.tag_field,
1047
+ tag=config.tag,
1048
+ array_like=config.array_like,
1049
+ forbid_unknown_fields=config.forbid_unknown_fields,
1050
+ )
1051
+
1052
+ hints = self._get_class_annotations(cls)
1053
+ npos = len(t.__struct_fields__) - len(t.__struct_defaults__)
1054
+ fields = []
1055
+ for name, encode_name, default_obj in zip(
1056
+ t.__struct_fields__,
1057
+ t.__struct_encode_fields__,
1058
+ (NODEFAULT,) * npos + t.__struct_defaults__,
1059
+ ):
1060
+ if default_obj is NODEFAULT:
1061
+ required = True
1062
+ default = default_factory = NODEFAULT
1063
+ elif isinstance(default_obj, _Factory):
1064
+ required = False
1065
+ default = NODEFAULT
1066
+ default_factory = default_obj.factory
1067
+ else:
1068
+ required = False
1069
+ default = NODEFAULT if default_obj is UNSET else default_obj
1070
+ default_factory = NODEFAULT
1071
+
1072
+ field = FieldNode(
1073
+ name=name,
1074
+ encode_name=encode_name,
1075
+ type=self.translate(hints[name]),
1076
+ required=required,
1077
+ default=default,
1078
+ default_factory=default_factory,
1079
+ )
1080
+ fields.append(field)
1081
+
1082
+ out.fields = tuple(fields)
1083
+ return out
1084
+ elif _is_typeddict(t):
1085
+ cls = t[args] if args else t
1086
+ if cls in self.cache:
1087
+ return self.cache[cls]
1088
+ self.cache[cls] = out = TypedDictType(cls, ())
1089
+ hints, required = _get_typeddict_info(cls)
1090
+ out.fields = tuple(
1091
+ FieldNode(
1092
+ name=name,
1093
+ encode_name=name,
1094
+ type=self.translate(field_type),
1095
+ required=name in required,
1096
+ )
1097
+ for name, field_type in sorted(hints.items())
1098
+ )
1099
+ return out
1100
+ elif _is_dataclass(t) or _is_attrs(t):
1101
+ cls = t[args] if args else t
1102
+ if cls in self.cache:
1103
+ return self.cache[cls]
1104
+ self.cache[cls] = out = DataclassType(cls, ())
1105
+ _, info, defaults, _, _ = _get_dataclass_info(cls)
1106
+ defaults = ((NODEFAULT,) * (len(info) - len(defaults))) + defaults
1107
+ fields = []
1108
+ for (name, typ, is_factory), default_obj in zip(info, defaults):
1109
+ if default_obj is NODEFAULT:
1110
+ required = True
1111
+ default = default_factory = NODEFAULT
1112
+ elif is_factory:
1113
+ required = False
1114
+ default = NODEFAULT
1115
+ default_factory = default_obj
1116
+ else:
1117
+ required = False
1118
+ default = NODEFAULT if default_obj is UNSET else default_obj
1119
+ default_factory = NODEFAULT
1120
+
1121
+ fields.append(
1122
+ FieldNode(
1123
+ name=name,
1124
+ encode_name=name,
1125
+ type=self.translate(typ),
1126
+ required=required,
1127
+ default=default,
1128
+ default_factory=default_factory,
1129
+ )
1130
+ )
1131
+ out.fields = tuple(fields)
1132
+ return out
1133
+ elif _is_namedtuple(t):
1134
+ cls = t[args] if args else t
1135
+ if cls in self.cache:
1136
+ return self.cache[cls]
1137
+ self.cache[cls] = out = NamedTupleType(cls, ())
1138
+ hints = self._get_class_annotations(cls)
1139
+ out.fields = tuple(
1140
+ FieldNode(
1141
+ name=name,
1142
+ encode_name=name,
1143
+ type=self.translate(hints.get(name, Any)),
1144
+ required=name not in t._field_defaults,
1145
+ default=t._field_defaults.get(name, NODEFAULT),
1146
+ )
1147
+ for name in t._fields
1148
+ )
1149
+ return out
1150
+ elif _is_pydantic(t):
1151
+ cls = t[args] if args else t
1152
+ if cls in self.cache:
1153
+ return self.cache[cls]
1154
+ self.cache[cls] = out = PydanticType(cls, ())
1155
+ _, info, defaults = _get_pydantic_info(cls)
1156
+ defaults = ((NODEFAULT,) * (len(info) - len(defaults))) + defaults
1157
+ fields = []
1158
+ for (name, typ, is_factory), default_obj in zip(info, defaults):
1159
+ is_required = default_obj is NODEFAULT
1160
+ if is_required:
1161
+ default = default_factory = NODEFAULT
1162
+ elif is_factory:
1163
+ default = NODEFAULT
1164
+ default_factory = default_obj
1165
+ else:
1166
+ default = default_obj
1167
+ default_factory = NODEFAULT
1168
+ field_info = cls.model_fields[name]
1169
+ encode_name = field_info.alias or field_info.serialization_alias or name
1170
+ fields.append(
1171
+ FieldNode(
1172
+ name=name,
1173
+ encode_name=encode_name,
1174
+ type=self.translate(typ),
1175
+ required=is_required,
1176
+ default=default,
1177
+ default_factory=default_factory,
1178
+ )
1179
+ )
1180
+ out.fields = tuple(fields)
1181
+ return out
1182
+ else:
1183
+ return CustomType(t)