11"""Lifecycle model for docbuild."""
22
3- from enum import Flag
3+ from enum import Flag , auto
44import re
5+ from typing import ClassVar , Self
56
6- from ..constants import ALLOWED_LIFECYCLES
77
8- _SEPARATOR = re .compile (r'[|,]' )
8+ class LifecycleFlag (Flag ):
9+ """LifecycleFlag represents the lifecycle of a product."""
910
11+ # Order is important here
12+ unknown = 0
13+ # UNKNOWN = 0
14+ """Unknown lifecycle state."""
1015
11- class BaseLifecycleFlag (Flag ):
12- """Base class for LifecycleFlag."""
16+ supported = auto ()
17+ """Supported lifecycle state."""
18+
19+ beta = auto ()
20+ """Beta lifecycle state."""
21+
22+ hidden = auto ()
23+ """Hidden lifecycle state."""
24+
25+ unsupported = auto ()
26+ """Unsupported lifecycle state."""
27+
28+ # NOTE: Putting a compiled regex (or other helper) as a class
29+ # variable on an Enum/Flag is error-prone:
30+ # the Enum metaclass treats class attributes specially and may
31+ # convert them into members or otherwise interfere.
32+ #
33+ # Solution: This class variable will be attached after class creation.
34+ # _SEPARATOR = re.compile(r'[|,]') # Static class variable
1335
1436 @classmethod
15- def from_str (cls , value : str ) -> 'BaseLifecycleFlag ' :
37+ def from_str (cls : 'LifecycleFlag' , value : str ) -> 'LifecycleFlag ' :
1638 """Convert a string to a LifecycleFlag object.
1739
1840 The string accepts the values 'supported', 'beta', 'hidden',
1941 'unsupported', or a combination of them separated by a comma or pipe.
20- Addtionally, the class knows the values "UNKNOWN" and " unknown".
21- An empty string, "", is equivalent to "UNKNOWN ".
42+ Addtionally, the class knows the values "unknown".
43+ An empty string, "", is equivalent to "unknown ".
2244
2345 Examples:
2446 >>> LifecycleFlag.from_str("supported")
2547 <LifecycleFlag.supported: 2>
26- >>> LifecycleFlag.from_str("supported| beta")
48+ >>> LifecycleFlag.from_str("supported, beta")
2749 <LifecycleFlag.supported|beta: 6>
2850 >>> LifecycleFlag.from_str("beta,supported|beta")
2951 <LifecycleFlag.supported|beta: 6>
3052 >>> LifecycleFlag.from_str("")
3153 <LifecycleFlag.unknown: 0>
3254
3355 """
56+ separator = cls ._SEPARATOR # will exist after we attach it
3457 try :
3558 flag = cls (0 ) # Start with an empty flag
36- parts = [v .strip () for v in _SEPARATOR .split (value ) if v .strip ()]
59+ parts = [v .strip () for v in separator .split (value ) if v .strip ()]
3760 if not parts :
3861 return cls (0 )
3962
4063 for part_name in parts :
41- flag |= cls [part_name ]
64+ flag |= cls . __members__ [part_name ]
4265
4366 return flag
4467
@@ -48,12 +71,12 @@ def from_str(cls, value: str) -> 'BaseLifecycleFlag':
4871 f'Invalid lifecycle name: { err .args [0 ]!r} . Allowed values: { allowed } ' ,
4972 ) from err
5073
51- def __contains__ (self , other : str | Flag ) -> bool :
74+ def __contains__ (self : Self , other : str | Flag ) -> bool :
5275 """Return True if self has at least one of same flags set as other.
5376
54- >>> "supported" in Lifecycle .beta
77+ >>> "supported" in LifecycleFlag .beta
5578 False
56- >>> "supported|beta" in Lifecycle .beta
79+ >>> "supported|beta" in LifecycleFlag .beta
5780 True
5881 """
5982 if isinstance (other , str ):
@@ -68,13 +91,5 @@ def __contains__(self, other: str | Flag) -> bool:
6891 return (self & item_flag ) == item_flag
6992
7093
71- # Lifecycle is implemented as a Flag as different states can be combined
72- # An additional "unknown" state could be used if the state is unknown or not yet
73- # retrieved.
74- # TODO: Should we allow weird combination like "supported|unsupported"
75- LifecycleFlag = BaseLifecycleFlag (
76- 'LifecycleFlag' ,
77- {'unknown' : 0 , 'UNKNOWN' : 0 }
78- | {item : (2 << index ) for index , item in enumerate (ALLOWED_LIFECYCLES , 0 )},
79- )
80- """LifecycleFlag represents the lifecycle of a product."""
94+ # attach after class creation so EnumMeta doesn't touch it
95+ LifecycleFlag ._SEPARATOR : ClassVar [re .Pattern ] = re .compile (r'[|,]' )
0 commit comments