Coverage for src/lexigram/features/decorators/feature_flag.py: 100%
25 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"""Async feature-flag–gated decorators.
3Provides :func:`feature_flag` and :func:`require_flag` for guarding async
4functions behind a named feature flag.
5"""
7from __future__ import annotations
9from collections.abc import Callable
10import functools
11from typing import TYPE_CHECKING, Any, TypeVar
13from lexigram.features.decorators._utils import _is_coroutinefunction, _resolve_manager
14from lexigram.features.exceptions import FeatureFlagDisabledError
16if TYPE_CHECKING:
17 from lexigram.features.manager.flag_manager import FlagManager
18 from lexigram.features.types import FlagContext
20F = TypeVar("F", bound=Callable[..., Any])
23def feature_flag(
24 name: str,
25 *,
26 manager: FlagManager | None = None,
27 fallback: Callable[..., Any] | None = None,
28 context: FlagContext | None = None,
29) -> Callable[[F], F]:
30 """Decorate an async function so it only runs when *name* is enabled.
32 When the flag is *disabled*:
34 * If *fallback* is provided it is called with the same arguments and its
35 return value is returned.
36 * Otherwise :class:`~lexigram.features.exceptions.FeatureFlagDisabledError`
37 is raised.
39 Args:
40 name: Feature flag name to check.
41 manager: Explicit :class:`~lexigram.features.manager.FlagManager`; resolved
42 via DI or a default when ``None``.
43 fallback: Optional callable invoked when the flag is disabled.
44 context: Optional evaluation context passed to the manager.
46 Returns:
47 A decorator for async functions.
48 """
50 def decorator(fn: F) -> F:
51 @functools.wraps(fn)
52 async def wrapper(*args: Any, **kwargs: Any) -> Any:
53 mgr = _resolve_manager(manager)
54 enabled = await mgr.is_enabled(name, context)
55 if enabled:
56 return await fn(*args, **kwargs)
57 if fallback is not None:
58 if _is_coroutinefunction(fallback):
59 return await fallback(*args, **kwargs)
60 return fallback(*args, **kwargs)
61 raise FeatureFlagDisabledError(name)
63 return wrapper # type: ignore[return-value]
65 return decorator
68def require_flag(
69 name: str,
70 *,
71 manager: FlagManager | None = None,
72 context: FlagContext | None = None,
73) -> Callable[[F], F]:
74 """Decorate an async function to raise when *name* is disabled.
76 Semantically identical to :func:`feature_flag` with no fallback, but
77 the intent is clearer when used as an access guard.
79 Args:
80 name: Feature flag name to check.
81 manager: Explicit manager; resolved via DI or a default when ``None``.
82 context: Optional evaluation context.
84 Returns:
85 A decorator for async functions.
86 """
87 return feature_flag(name, manager=manager, context=context)
90__all__ = ["feature_flag", "require_flag"]