Coverage for src/lexigram/features/decorators/sync.py: 97%
31 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"""Synchronous feature-flag–gated decorators.
3Provides :func:`feature_flag_sync` and :func:`require_flag_sync` for
4guarding synchronous functions behind a named feature flag. Only suitable
5when the backing provider has an in-memory evaluation path.
6"""
8from __future__ import annotations
10from collections.abc import Callable
11import functools
12from typing import TYPE_CHECKING, Any, TypeVar
14from lexigram.features.decorators._utils import _resolve_manager
15from lexigram.features.exceptions import FeatureFlagDisabledError
17if TYPE_CHECKING:
18 from lexigram.features.manager.flag_manager import FlagManager
19 from lexigram.features.types import FlagContext
21F = TypeVar("F", bound=Callable[..., Any])
24def feature_flag_sync(
25 name: str,
26 *,
27 manager: FlagManager | None = None,
28 fallback: Callable[..., Any] | None = None,
29 context: FlagContext | None = None,
30) -> Callable[[F], F]:
31 """Decorate a **synchronous** function so it only runs when *name* is enabled.
33 Evaluation uses :meth:`~lexigram.features.backends.base.AbstractFlagProvider.evaluate_sync`
34 on the underlying provider.
36 Args:
37 name: Feature flag name to check.
38 manager: Explicit manager; resolved via DI or a default when ``None``.
39 fallback: Optional callable invoked when the flag is disabled.
40 context: Optional evaluation context.
42 Returns:
43 A decorator for synchronous functions.
44 """
46 def decorator(fn: F) -> F:
47 @functools.wraps(fn)
48 def wrapper(*args: Any, **kwargs: Any) -> Any:
49 mgr = _resolve_manager(manager)
50 override = mgr.get_override_state(name)
51 if override is True:
52 return fn(*args, **kwargs)
53 if override is False:
54 if fallback is not None:
55 return fallback(*args, **kwargs)
56 raise FeatureFlagDisabledError(name)
57 provider = mgr.provider
58 result = provider.evaluate_sync(name, context)
59 if result.enabled:
60 return fn(*args, **kwargs)
61 if fallback is not None:
62 return fallback(*args, **kwargs)
63 raise FeatureFlagDisabledError(name)
65 return wrapper # type: ignore[return-value]
67 return decorator
70def require_flag_sync(
71 name: str,
72 *,
73 manager: FlagManager | None = None,
74 context: FlagContext | None = None,
75) -> Callable[[F], F]:
76 """Synchronous variant of :func:`~lexigram.features.decorators.require_flag`.
78 Raises :class:`~lexigram.features.exceptions.FeatureFlagDisabledError`
79 when *name* is disabled.
81 Args:
82 name: Feature flag name to check.
83 manager: Explicit manager; resolved via DI or a default when ``None``.
84 context: Optional evaluation context.
86 Returns:
87 A decorator for synchronous functions.
88 """
89 return feature_flag_sync(name, manager=manager, context=context)
92__all__ = ["feature_flag_sync", "require_flag_sync"]