5757S = t .TypeVar ("S" )
5858
5959
60+ _lazy_annotations_ : bool = sys .version_info [:2 ] >= (3 , 14 )
61+ """
62+ Annotations are loaded after enum member definitions in python 3.14+
63+ """
64+
65+
6066def with_typehint (baseclass : t .Type [T ]) -> t .Type [T ]:
6167 """
6268 This is icky but it works - revisit in future.
@@ -435,6 +441,12 @@ class _PropertyEnumDict(class_dict.__class__): # type: ignore[name-defined]
435441 """
436442
437443 _ep_properties_ = properties
444+
445+ # lazy properties in annotation declaration order
446+ _lazy_properties_ : t .List [_Prop ] = []
447+
448+ # member -> value tuple
449+ _lazy_property_values_ : t .Dict [str , t .Any ] = {}
438450 _specialized_ : t .Dict [str , t .Dict [str , _Specialized ]] = {}
439451 _ids_ : t .Dict [int , str ] = {}
440452 _member_names : t .Union [t .List [str ], t .Dict [str , t .Any ]]
@@ -479,7 +491,10 @@ def __setitem__(self, key, value):
479491 else :
480492 self .class_dict ["_symmetric_builtins_" ] = [prop ]
481493 else :
482- self .class_dict ._ep_properties_ [prop ()] = []
494+ if _lazy_annotations_ :
495+ self .class_dict ._lazy_properties_ .append (prop ())
496+ else :
497+ self .class_dict ._ep_properties_ [prop ()] = []
483498 super ().__setitem__ (key , value )
484499
485500 def __init__ (self ):
@@ -490,6 +505,32 @@ def __init__(self):
490505 self [item ] = value
491506 self ._create_properties_ = not self ._ep_properties_
492507
508+ def add_member_and_properties (self , key : str , value : t .Any ) -> t .Any :
509+ try :
510+ num_vals = len (value ) - len (self ._ep_properties_ )
511+ if num_vals < 1 or len (self ._ep_properties_ ) != len (
512+ value [num_vals :]
513+ ):
514+ raise ValueError (
515+ f"{ key } must have "
516+ f"{ len (self ._ep_properties_ )} property "
517+ f"values."
518+ )
519+ idx = num_vals
520+ for values in self ._ep_properties_ .values ():
521+ values .append (value [idx ])
522+ idx += 1
523+
524+ if num_vals == 1 :
525+ return value [0 ]
526+ else :
527+ return value [0 :num_vals ]
528+
529+ except TypeError as type_err :
530+ raise ValueError (
531+ f"{ key } must have { len (self ._ep_properties_ )} property values."
532+ ) from type_err
533+
493534 def __setitem__ (self , key , value ):
494535 if isinstance (value , _Specialized ):
495536 for en_val in value .ids :
@@ -505,15 +546,18 @@ def __setitem__(self, key, value):
505546 dict .__setitem__ (self , key , value )
506547 elif key in EnumPropertiesMeta .RESERVED :
507548 raise ValueError (f"{ key } is reserved." )
508- elif self ._ep_properties_ :
549+ elif self ._ep_properties_ or (
550+ _lazy_annotations_ and isinstance (value , tuple )
551+ ):
509552 member_names = getattr (class_dict , "_member_names" )
510553 # are we an enum value? - just kick this up to parent class
511554 # logic, this code runs once on load - its fine that it's
512555 # doing a little redundant work and doing it this way
513556 # ensures robust fidelity to Enum behavior.
514557 before = len (member_names )
515558 class_dict [key ] = value
516- self ._create_properties_ = False # we're done with annotations
559+ # are we done with annotations?
560+ self ._create_properties_ = _lazy_annotations_
517561 remove = False
518562 if (
519563 len (member_names ) > before
@@ -524,28 +568,17 @@ def __setitem__(self, key, value):
524568 not isinstance (value , type )
525569 ):
526570 self .__first_class_members__ .append (key )
527- try :
528- num_vals = len (value ) - len (self ._ep_properties_ )
529- if num_vals < 1 or len (self ._ep_properties_ ) != len (
530- value [num_vals :]
531- ):
532- raise ValueError (
533- f"{ key } must have "
534- f"{ len (self ._ep_properties_ )} property "
535- f"values."
536- )
537- idx = num_vals
538- for values in self ._ep_properties_ .values ():
539- values .append (value [idx ])
540- idx += 1
541-
571+ if _lazy_annotations_ and not self ._ep_properties_ :
572+ self ._lazy_property_values_ [key ] = value
573+ # we set the value of the member to the first value in the
574+ # tuple - this is important to do here because it allows
575+ # members to be used as their value element later on in the
576+ # declaration - think named composite flag values - we may
577+ # have to change this later because we do not know what our
578+ # properties are yet
542579 value = value [0 ]
543-
544- except TypeError as type_err :
545- raise ValueError (
546- f"{ key } must have { len (self ._ep_properties_ )} "
547- f"property values."
548- ) from type_err
580+ else :
581+ value = self .add_member_and_properties (key , value )
549582
550583 elif key in member_names :
551584 remove = True # pragma: no cover
@@ -569,10 +602,8 @@ def __setitem__(self, key, value):
569602 before = len (self ._member_names )
570603 super ().__setitem__ (key , value )
571604 if len (self ._member_names ) > before :
572- self ._create_properties_ = False
605+ self ._create_properties_ = _lazy_annotations_
573606
574- if sys .version_info [:2 ] >= (3 , 14 ):
575- return {"_ep_enum_dict_" : _PropertyEnumDict ()}
576607 return _PropertyEnumDict () # type: ignore[no-untyped-call]
577608
578609 def __new__ (mcs , classname , bases , classdict , ** kwargs ):
@@ -590,24 +621,39 @@ def __new__(mcs, classname, bases, classdict, **kwargs):
590621 incorrectly, or if non-hashable values are provided for a
591622 symmetric property.
592623 """
593- if sys .version_info [:2 ] >= (3 , 14 ):
624+ if _lazy_annotations_ and not classdict ._ep_properties_ :
625+ """
626+ In python 3.14+ annotations are loaded after enum members are
627+ defined, so we have to reconcile our properties here.
628+ """
594629 from annotationlib import (
595630 Format ,
596631 call_annotate_function ,
597632 get_annotate_from_class_namespace ,
598633 )
599634
600- lazy_dict = classdict .pop ("_ep_enum_dict_" )
601635 annotate = get_annotate_from_class_namespace (classdict )
602636 if annotate :
603- lazy_dict ["__annotations__" ] = {}
637+ classdict ["__annotations__" ] = {}
604638 for attr , typ in call_annotate_function (
605639 annotate , format = Format .VALUE
606640 ).items ():
607- lazy_dict ["__annotations__" ][attr ] = typ # or other formats
608- for key , value in classdict .items ():
609- lazy_dict [key ] = value
610- classdict = lazy_dict
641+ classdict ["__annotations__" ][attr ] = typ # or other formats
642+
643+ if classdict ._lazy_property_values_ :
644+ classdict ._ep_properties_ = {
645+ prop : [] for prop in classdict ._lazy_properties_
646+ }
647+ for en , value in classdict ._lazy_property_values_ .items ():
648+ real_value = classdict .add_member_and_properties (en , value )
649+ if real_value != classdict [en ] and isinstance (real_value , tuple ):
650+ # if we're here - our assumption that the value was the first
651+ # element of the member tuple was wrong - reset it to the real
652+ # value bypassing EnumDict checks
653+ dict .__setitem__ (classdict , en , real_value )
654+
655+ classdict ._lazy_properties_ .clear ()
656+ classdict ._lazy_property_values_ .clear ()
611657
612658 cls = super ().__new__ (
613659 mcs ,
0 commit comments