Feature Flags Reference¶
API reference for the feature flag system.
Engine methods¶
engine.use_openfeature()¶
Activate the feature flag subsystem. Call once before any flag evaluation or flag/segment CRUD.
engine.flag_client¶
OpenFeature-compatible async flag client. Available after use_openfeature().
value = await engine.flag_client.get_boolean_value(flag_key, default, context)
value = await engine.flag_client.get_string_value(flag_key, default, context)
value = await engine.flag_client.get_integer_value(flag_key, default, context)
value = await engine.flag_client.get_float_value(flag_key, default, context)
value = await engine.flag_client.get_object_value(flag_key, default, context)
| Parameter | Type | Description |
|---|---|---|
flag_key |
str |
The flag's unique key |
default |
Any |
Returned when the flag is not found or an error occurs |
context |
EvaluationContext |
Per-request context for targeting |
engine.sync.flag_client¶
Thread-safe synchronous version for def (non-async) route handlers.
Accepts EvaluationContext objects or plain dicts ({"targeting_key": user_id, ...}).
await engine.save_flag(flag, *, actor, platform, action, audit)¶
Create or replace a flag. Writes an audit log entry unless audit=False.
| Parameter | Type | Default | Description |
|---|---|---|---|
flag |
FeatureFlag |
required | Flag to persist |
actor |
str |
"system" |
Identity recorded in the audit log |
platform |
str |
"" |
Surface recorded in the audit log ("dashboard", "cli", etc.) |
action |
str \| None |
None |
Override the audit action string. Defaults to flag_created or flag_updated based on whether the flag already existed |
audit |
bool |
True |
Set to False to skip writing an audit log entry. Use this for startup seeds and programmatic initialization |
# Normal save — audited
await engine.save_flag(FeatureFlag(key="my-flag", ...))
# Startup seed — no audit entry
await engine.save_flag(FeatureFlag(key="my-flag", ...), audit=False)
await engine.get_flag(key)¶
Return the FeatureFlag for key, or None if not found.
await engine.list_flags()¶
Return all flags as a list.
await engine.delete_flag(key, *, actor, platform, audit)¶
Delete a flag. Writes an audit log entry unless audit=False.
| Parameter | Type | Default | Description |
|---|---|---|---|
key |
str |
required | Key of the flag to delete |
actor |
str |
"system" |
Identity recorded in the audit log |
platform |
str |
"" |
Surface recorded in the audit log |
audit |
bool |
True |
Set to False to skip the audit entry |
await engine.save_segment(segment, *, actor, platform, audit)¶
Create or replace a segment. Writes an audit log entry unless audit=False.
| Parameter | Type | Default | Description |
|---|---|---|---|
segment |
Segment |
required | Segment to persist |
actor |
str |
"system" |
Identity recorded in the audit log |
platform |
str |
"" |
Surface recorded in the audit log |
audit |
bool |
True |
Set to False to skip writing an audit log entry. Use this for startup seeds and programmatic initialization |
# Startup seed — no audit entry
await engine.save_segment(Segment(key="beta-users", ...), audit=False)
await engine.get_segment(key)¶
Return the Segment for key, or None.
await engine.list_segments()¶
Return all segments as a list.
await engine.delete_segment(key, *, actor, platform, audit)¶
Delete a segment. Writes an audit log entry unless audit=False.
| Parameter | Type | Default | Description |
|---|---|---|---|
key |
str |
required | Key of the segment to delete |
actor |
str |
"system" |
Identity recorded in the audit log |
platform |
str |
"" |
Surface recorded in the audit log |
audit |
bool |
True |
Set to False to skip the audit entry |
Models¶
FeatureFlag¶
Definition of a feature flag.
class FeatureFlag(BaseModel):
key: str
name: str
description: str = ""
type: FlagType
tags: list[str] = []
variations: list[FlagVariation]
off_variation: str
fallthrough: str | list[RolloutVariation]
enabled: bool = True
prerequisites: list[Prerequisite] = []
targets: dict[str, list[str]] = {}
rules: list[TargetingRule] = []
scheduled_changes: list[ScheduledChange] = []
status: FlagStatus = FlagStatus.ACTIVE
temporary: bool = True
maintainer: str | None = None
created_at: datetime
updated_at: datetime
created_by: str = "system"
| Field | Description |
|---|---|
key |
Unique identifier used in code: get_boolean_value("my-flag", ...) |
name |
Human-readable display name |
type |
FlagType.BOOLEAN, STRING, INTEGER, FLOAT, or JSON |
variations |
All possible values; must contain at least two |
off_variation |
Variation served when enabled=False |
fallthrough |
Default when no rule matches: a variation name (str) or a percentage rollout (list[RolloutVariation]) |
enabled |
Kill-switch. False means all requests get off_variation |
prerequisites |
Flags that must pass before this flag's rules run |
targets |
Individual targeting: {"on": ["user_1", "user_2"]} |
rules |
Targeting rules evaluated top-to-bottom; first match wins |
FlagType¶
class FlagType(StrEnum):
BOOLEAN = "boolean"
STRING = "string"
INTEGER = "integer"
FLOAT = "float"
JSON = "json"
FlagVariation¶
One possible value a flag can return.
class FlagVariation(BaseModel):
name: str # e.g. "on", "off", "control", "variant_a"
value: bool | str | int | float | dict | list
description: str = ""
RolloutVariation¶
One bucket in a percentage rollout (used in fallthrough or TargetingRule.rollout).
class RolloutVariation(BaseModel):
variation: str # references FlagVariation.name
weight: int # share of traffic, out of 100_000 total
Weights in a rollout list must sum to 100_000. Examples:
| Percentage | Weight |
|---|---|
| 10% | 10_000 |
| 25% | 25_000 |
| 33.33% | 33_333 |
| 50% | 50_000 |
TargetingRule¶
A rule that matches clauses and serves a variation.
class TargetingRule(BaseModel):
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
description: str = ""
clauses: list[RuleClause] = []
variation: str | None = None # mutually exclusive with rollout
rollout: list[RolloutVariation] | None = None
track_events: bool = False
Clauses within a rule are AND-ed. Rules are evaluated top-to-bottom; first match wins.
RuleClause¶
A single condition within a targeting rule.
class RuleClause(BaseModel):
attribute: str # context attribute to inspect, e.g. "plan", "country", "email"
operator: Operator # comparison to apply
values: list[Any] # one or more values; multiple values use OR logic
negate: bool = False # invert the result
Operator¶
All supported targeting operators.
class Operator(StrEnum):
# Equality
IS = "is"
IS_NOT = "is_not"
# String
CONTAINS = "contains"
NOT_CONTAINS = "not_contains"
STARTS_WITH = "starts_with"
ENDS_WITH = "ends_with"
MATCHES = "matches" # Python regex
NOT_MATCHES = "not_matches"
# Numeric
GT = "gt"
GTE = "gte"
LT = "lt"
LTE = "lte"
# Date (ISO-8601 lexicographic)
BEFORE = "before"
AFTER = "after"
# Collection
IN = "in"
NOT_IN = "not_in"
# Segment
IN_SEGMENT = "in_segment"
NOT_IN_SEGMENT = "not_in_segment"
# Semantic version (requires `packaging`)
SEMVER_EQ = "semver_eq"
SEMVER_LT = "semver_lt"
SEMVER_GT = "semver_gt"
Prerequisite¶
A flag that must evaluate to a specific variation before the dependent flag runs.
class Prerequisite(BaseModel):
flag_key: str # key of the prerequisite flag
variation: str # variation the prerequisite must return
If the prerequisite returns any other variation, the dependent flag serves off_variation with reason PREREQUISITE_FAIL.
Segment¶
A reusable group of users for flag targeting.
class Segment(BaseModel):
key: str
name: str
description: str = ""
included: list[str] = [] # context keys always in the segment
excluded: list[str] = [] # context keys always excluded (overrides rules + included)
rules: list[SegmentRule] = []
tags: list[str] = []
created_at: datetime
updated_at: datetime
Evaluation order for context key k:
kinexcluded→ not in segmentkinincluded→ in segment- Any
SegmentRulematches → in segment - Otherwise → not in segment
SegmentRule¶
An attribute-based rule inside a segment. Multiple segment rules are OR-ed: if any rule matches, the user is in the segment.
class SegmentRule(BaseModel):
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
description: str = ""
clauses: list[RuleClause] = [] # all must match (AND logic)
EvaluationContext¶
Per-request context for flag targeting and rollout bucketing.
class EvaluationContext(BaseModel):
key: str # required — user/session/org ID
kind: str = "user" # context kind
email: str | None = None
ip: str | None = None
country: str | None = None
app_version: str | None = None
attributes: dict[str, Any] = {} # any additional attributes
Named fields (email, ip, country, app_version) are accessible in rule clauses by the same names. Items in attributes are merged in and accessible by key.
ResolutionDetails¶
Full result of a flag evaluation, surfaced in hooks.
class ResolutionDetails(BaseModel):
value: Any
variation: str | None = None
reason: EvaluationReason
rule_id: str | None = None # set when reason == RULE_MATCH
prerequisite_key: str | None = None # set when reason == PREREQUISITE_FAIL
error_message: str | None = None # set when reason == ERROR
EvaluationReason¶
Why a specific value was returned.
| Value | Description |
|---|---|
OFF |
Flag is globally disabled. off_variation was served. |
TARGET_MATCH |
Context key was in the individual targets list. |
RULE_MATCH |
A targeting rule matched. rule_id is set. |
FALLTHROUGH |
No targeting rule matched. Default rule was served. |
PREREQUISITE_FAIL |
A prerequisite flag did not return the required variation. |
ERROR |
Provider or evaluation error. Default value was returned. |
DEFAULT |
Flag not found. SDK default was returned. |
REST API¶
When ShieldAdmin is mounted with engine.use_openfeature(), these endpoints are registered under the admin path (e.g. /shield/api/):
Flags¶
| Method | Path | Description |
|---|---|---|
GET |
/api/flags |
List all flags |
POST |
/api/flags |
Create a flag (full FeatureFlag body) |
GET |
/api/flags/{key} |
Get a single flag |
PUT |
/api/flags/{key} |
Replace a flag (full update) |
PATCH |
/api/flags/{key} |
Partial update |
DELETE |
/api/flags/{key} |
Delete a flag |
POST |
/api/flags/{key}/enable |
Enable (kill-switch off) |
POST |
/api/flags/{key}/disable |
Disable (kill-switch on) |
POST |
/api/flags/{key}/evaluate |
Evaluate for a given context |
Segments¶
| Method | Path | Description |
|---|---|---|
GET |
/api/segments |
List all segments |
POST |
/api/segments |
Create a segment |
GET |
/api/segments/{key} |
Get a single segment |
PUT |
/api/segments/{key} |
Replace a segment |
DELETE |
/api/segments/{key} |
Delete a segment |
Evaluation algorithm¶
The evaluator (FlagEvaluator) is pure Python with no I/O, unit-testable in isolation.
from shield.core.feature_flags.evaluator import FlagEvaluator
evaluator = FlagEvaluator(segments={"beta": beta_segment})
result = evaluator.evaluate(flag, ctx, all_flags)
print(result.value, result.reason)
Rollout bucketing uses SHA-1 of "{flag_key}:{ctx.kind}:{ctx.key}" modulo 100_000. The same context always lands in the same bucket; bucketing is stable across restarts and deploys.
Prerequisite recursion is limited to depth 10. Circular dependencies are rejected at write time by engine.save_flag().
Dashboard routes¶
| URL | Page |
|---|---|
/shield/flags |
Flag list with search and status filters |
/shield/flags/{key} |
Flag detail (4 tabs: Overview, Targeting, Variations, Settings) |
/shield/segments |
Segment list |
Example¶
Example: examples/fastapi/feature_flags.py