11import enum
2+ import sys
23from collections import namedtuple
34from inspect import Signature , Parameter
45
@@ -112,24 +113,29 @@ def do_recurse(result, libs):
112113variadic = namedtuple ('variadic' , ['type' ])
113114
114115
115- class OptionMeta (type ):
116- def __new__ (cls , name , bases , attrs ):
117- attrs .setdefault ('__annotations__' , {})
118- slots = tuple (attrs ['__annotations__' ].keys ())
119- defaults = {}
120- for i in slots :
121- if i in attrs :
122- defaults [i ] = attrs .pop (i )
116+ def _get_ns_annotations (attrs ):
117+ if '__annotations__' in attrs :
118+ return attrs ['__annotations__' ]
123119
124- attrs .update ({'__slots__' : slots , '_field_defaults' : defaults })
125- return type .__new__ (cls , name , bases , attrs )
120+ if sys .version_info >= (3 , 14 ):
121+ import annotationlib
122+ annotate = annotationlib .get_annotate_from_class_namespace (attrs )
123+ if annotate :
124+ return annotationlib .call_annotate_function (
125+ annotate , format = annotationlib .Format .FORWARDREF
126+ )
127+ return {}
126128
127129
128- # This is like `typing.NamedTuple`, except with runtime type checking,
129- # promoting strings to enums, and allowing variadic values.
130- class Option (metaclass = OptionMeta ):
131- registry = {}
130+ def _get_annotations (thing ):
131+ typ = thing if isinstance (thing , type ) else type (thing )
132+ if sys .version_info >= (3 , 14 ):
133+ import annotationlib
134+ return annotationlib .get_annotations (typ )
135+ return typ .__dict__ .get ('__annotations__' , {})
132136
137+
138+ class OptionMeta (type ):
133139 @staticmethod
134140 def __make_parameters (fields , defaults ):
135141 has_variadic = False
@@ -144,6 +150,28 @@ def __make_parameters(fields, defaults):
144150 default = defaults .get (k , Parameter .empty )
145151 yield Parameter (k , kind , default = default )
146152
153+ def __new__ (cls , name , bases , attrs ):
154+ annotations = _get_ns_annotations (attrs )
155+ slots = tuple (annotations .keys ())
156+ defaults = {}
157+ for i in slots :
158+ if i in attrs :
159+ defaults [i ] = attrs .pop (i )
160+
161+ attrs .update ({
162+ '__slots__' : slots ,
163+ '_signature' : Signature (list (cls .__make_parameters (
164+ annotations , defaults
165+ )))
166+ })
167+ return type .__new__ (cls , name , bases , attrs )
168+
169+
170+ # This is like `typing.NamedTuple`, except with runtime type checking,
171+ # promoting strings to enums, and allowing variadic values.
172+ class Option (metaclass = OptionMeta ):
173+ registry = {}
174+
147175 @staticmethod
148176 def __check_type (typ , value ):
149177 if not typ or isinstance (value , typ ):
@@ -164,14 +192,12 @@ def __init_subclass__(cls, **kwargs):
164192 cls .registry [cls .__name__ ] = cls
165193
166194 def __init__ (self , * args , ** kwargs ):
167- sig = Signature (list (self .__make_parameters (
168- self .__annotations__ , self ._field_defaults
169- )))
170- bound = sig .bind (* args , ** kwargs )
195+ bound = self ._signature .bind (* args , ** kwargs )
171196 bound .apply_defaults ()
172197
198+ annotations = _get_annotations (self )
173199 for name , value in bound .arguments .items ():
174- typ = self . __annotations__ [name ]
200+ typ = annotations [name ]
175201 if isinstance (typ , variadic ):
176202 value = [self .__check_type (typ .type , i ) for i in value ]
177203 else :
0 commit comments