Coverage for src/lexigram/features/__init__.py: 83%

18 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-26 02:04 +0800

1"""Feature flag subsystem for the Lexigram framework. 

2 

3Provides a full-featured, async-first feature flag system with TTL caching, 

4runtime overrides, variant A/B testing, and DI integration. 

5 

6Exports: 

7 Flag: Full flag definition including evaluation rules. 

8 FlagType: Evaluation strategy enum (BOOLEAN, PERCENTAGE, USER_LIST, etc.). 

9 FlagContext: Evaluation context (user ID, attributes, session). 

10 FlagEvaluation: Result of evaluating a flag for a given context. 

11 FlagValue: Type alias for boolean or variant-name evaluation result. 

12 FeatureFlagError: Base exception for all feature-flag errors. 

13 FlagNotFoundError: Raised when a requested flag does not exist. 

14 FlagEvaluationError: Raised when a provider fails during flag evaluation. 

15 FeatureFlagDisabledError: Raised when a feature-guarded path is called disabled. 

16 AbstractFlagProvider: Rich abstract base with full evaluation logic. 

17 LocalProvider: In-memory, code-defined flag store. 

18 EnvProvider: Reads flags from environment variables. 

19 ChainedProvider: Layered lookup across multiple providers. 

20 MemoryProvider: Lightweight test double with override support. 

21 FlagManager: Central manager with caching, overrides, and variant support. 

22 FlagChangeListener: Sync callback type for flag override change notifications. 

23 AsyncFlagChangeListener: Async callback type for flag override change notifications. 

24 ManagerConfig: Configuration dataclass for FlagManager. 

25 FeatureFlagsConfig: Runtime configuration for the DI provider. 

26 FeatureFlagsProvider: DI provider registering flag infrastructure. 

27 feature_flag: Decorator for async flag-gated functions. 

28 feature_flag_sync: Decorator for sync flag-gated functions. 

29 require_flag: Decorator that raises when a flag is disabled (async). 

30 require_flag_sync: Decorator that raises when a flag is disabled (sync). 

31 CacheBackendFlagProvider: Cache-backed flag provider (Redis, Memcached, etc.). 

32 FlagProviderProtocol: Contract for feature flag providers. 

33 MutableFlagProviderProtocol: Contract for mutable feature flag providers. 

34 FlagManagerProtocol: Contract for feature flag managers. 

35""" 

36 

37from __future__ import annotations 

38 

39import importlib.metadata 

40from typing import TYPE_CHECKING 

41 

42__path__ = __import__("pkgutil").extend_path(__path__, __name__) 

43 

44# -- Version ------------------------------------------------------------------- 

45 

46from lexigram.features.constants import __version__ as __version__ 

47 

48if TYPE_CHECKING: 

49 from lexigram.features.backends.base import AbstractFlagProvider 

50 from lexigram.features.backends.cache import CacheBackendFlagProvider 

51 from lexigram.features.backends.chained import ChainedProvider 

52 from lexigram.features.backends.env import EnvProvider 

53 from lexigram.features.backends.local import LocalProvider 

54 from lexigram.features.backends.testing import MemoryProvider 

55 from lexigram.features.config import FeatureFlagsConfig 

56 from lexigram.features.decorators import ( 

57 feature_flag, 

58 feature_flag_sync, 

59 require_flag, 

60 require_flag_sync, 

61 ) 

62 from lexigram.features.di.provider import FeatureFlagsProvider 

63 from lexigram.features.events import FlagChangeEvent 

64 from lexigram.features.exceptions import ( 

65 FeatureFlagDisabledError, 

66 FeatureFlagError, 

67 FlagEvaluationError, 

68 FlagNotFoundError, 

69 ) 

70 from lexigram.features.manager import ( 

71 AsyncFlagChangeListener, 

72 FlagChangeListener, 

73 FlagManager, 

74 ManagerConfig, 

75 ) 

76 from lexigram.features.protocols import ( 

77 FlagManagerProtocol, 

78 FlagProviderProtocol, 

79 MutableFlagProviderProtocol, 

80 ) 

81 from lexigram.features.types import ( 

82 Flag, 

83 FlagContext, 

84 FlagEvaluation, 

85 FlagType, 

86 FlagValue, 

87 ) 

88 

89_LAZY_IMPORTS: dict[str, str] = { 

90 # module 

91 "FeatureFlagsModule": "lexigram.features.module", 

92 # protocols 

93 "FlagProviderProtocol": "lexigram.features.protocols", 

94 "MutableFlagProviderProtocol": "lexigram.features.protocols", 

95 "FlagManagerProtocol": "lexigram.features.protocols", 

96 # types 

97 "Flag": "lexigram.features.types", 

98 "FlagContext": "lexigram.features.types", 

99 "FlagEvaluation": "lexigram.features.types", 

100 "FlagType": "lexigram.features.types", 

101 "FlagValue": "lexigram.features.types", 

102 # exceptions 

103 "FeatureFlagDisabledError": "lexigram.features.exceptions", 

104 "FeatureFlagError": "lexigram.features.exceptions", 

105 "FlagEvaluationError": "lexigram.features.exceptions", 

106 "FlagNotFoundError": "lexigram.features.exceptions", 

107 # backends 

108 "AbstractFlagProvider": "lexigram.features.backends.base", 

109 "CacheBackendFlagProvider": "lexigram.features.backends.cache", 

110 "LocalProvider": "lexigram.features.backends.local", 

111 "EnvProvider": "lexigram.features.backends.env", 

112 "ChainedProvider": "lexigram.features.backends.chained", 

113 "MemoryProvider": "lexigram.features.backends.testing", 

114 # manager 

115 "FlagManager": "lexigram.features.manager", 

116 "AsyncFlagChangeListener": "lexigram.features.manager", 

117 "FlagChangeListener": "lexigram.features.manager", 

118 "ManagerConfig": "lexigram.features.manager", 

119 # events 

120 "FlagChangeEvent": "lexigram.features.events", 

121 # decorators 

122 "feature_flag": "lexigram.features.decorators", 

123 "feature_flag_sync": "lexigram.features.decorators", 

124 "require_flag": "lexigram.features.decorators", 

125 "require_flag_sync": "lexigram.features.decorators", 

126 # config 

127 "FeatureFlagsConfig": "lexigram.features.config", 

128 # integration 

129 "FeatureFlagsProvider": "lexigram.features.di.provider", 

130 # Hooks 

131 "FeatureFlagEvaluatedHook": "lexigram.features.hooks", 

132 "FeatureFlagUpdatedHook": "lexigram.features.hooks", 

133} 

134 

135 

136def __getattr__(name: str) -> object: 

137 if name in _LAZY_IMPORTS: 

138 import importlib 

139 

140 module = importlib.import_module(_LAZY_IMPORTS[name]) 

141 value = getattr(module, name) 

142 globals()[name] = value 

143 return value 

144 msg = f"module {__name__!r} has no attribute {name!r}" 

145 raise AttributeError(msg) 

146 

147 

148def __dir__() -> list[str]: 

149 return sorted(set(__all__) | set(_LAZY_IMPORTS.keys())) 

150 

151 

152__all__ = list(_LAZY_IMPORTS.keys())