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
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-26 02:04 +0800
1"""Feature flag subsystem for the Lexigram framework.
3Provides a full-featured, async-first feature flag system with TTL caching,
4runtime overrides, variant A/B testing, and DI integration.
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"""
37from __future__ import annotations
39import importlib.metadata
40from typing import TYPE_CHECKING
42__path__ = __import__("pkgutil").extend_path(__path__, __name__)
44# -- Version -------------------------------------------------------------------
46from lexigram.features.constants import __version__ as __version__
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 )
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}
136def __getattr__(name: str) -> object:
137 if name in _LAZY_IMPORTS:
138 import importlib
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)
148def __dir__() -> list[str]:
149 return sorted(set(__all__) | set(_LAZY_IMPORTS.keys()))
152__all__ = list(_LAZY_IMPORTS.keys())