flagdrop-sdk 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: flagdrop-sdk
3
+ Version: 0.1.0
4
+ Summary: FlagDrop Python SDK — evaluate feature flags from your cloud bucket
5
+ Author-email: FlagDrop <support@flagdrop.io>
6
+ License: MIT
7
+ Project-URL: Homepage, https://flagdrop.io
8
+ Project-URL: Documentation, https://flagdrop.io/docs
9
+ Keywords: feature-flags,flagdrop,sdk,s3,feature-toggles
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: boto3>=1.28.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=7.0; extra == "dev"
25
+ Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
26
+ Requires-Dist: moto[s3]>=5.0; extra == "dev"
27
+
28
+ # FlagDrop Python SDK
29
+
30
+ Feature flag evaluation that runs entirely in your cloud. No vendor servers, no data leaving your infrastructure.
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install flagdrop-sdk
36
+ ```
37
+
38
+ ## Quick Start
39
+
40
+ ```python
41
+ from flagdrop import FlagClient
42
+
43
+ client = FlagClient(
44
+ bucket="my-app-flags",
45
+ environment="production",
46
+ provider="aws",
47
+ region="us-east-1",
48
+ )
49
+ client.initialize()
50
+
51
+ # Boolean flag
52
+ enabled = client.get_bool("new-checkout", False)
53
+
54
+ # String flag with targeting context
55
+ theme = client.get_string("app-theme", "light", {"plan": "enterprise"})
56
+
57
+ # Number flag
58
+ max_items = client.get_number("max-items", 10)
59
+
60
+ # JSON flag
61
+ config = client.get_json("feature-config", {"limit": 5})
62
+ ```
63
+
64
+ ## How It Works
65
+
66
+ 1. You define flags in the [FlagDrop dashboard](https://flagdrop.io)
67
+ 2. FlagDrop pushes a JSON config file to an S3 bucket in **your** AWS account
68
+ 3. The SDK reads that file and evaluates flags locally at runtime
69
+
70
+ No network calls to FlagDrop servers during evaluation. No latency. No single point of failure.
71
+
72
+ ## Configuration
73
+
74
+ | Parameter | Description |
75
+ |-----------|-------------|
76
+ | `bucket` | S3 bucket name where flag configs are stored |
77
+ | `environment` | Environment name (e.g., `production`, `staging`) |
78
+ | `provider` | Cloud provider (`aws`) |
79
+ | `region` | AWS region (e.g., `us-east-1`) |
80
+ | `scope` | Config scope: `backend` (default) or `frontend` |
81
+ | `refresh_interval_seconds` | How often to re-fetch config (default: 30s, 0 to disable) |
82
+
83
+ ## Targeting Rules
84
+
85
+ The SDK evaluates targeting rules locally. Supported operators:
86
+
87
+ - `eq`, `neq` — exact match / not equal
88
+ - `in`, `notIn` — value in list / not in list
89
+ - `lt`, `gt` — less than / greater than (numeric)
90
+ - `startsWith`, `endsWith`, `contains` — string matching
91
+ - `segment` — segment membership
92
+
93
+ ## Rollouts
94
+
95
+ Percentage-based rollouts use deterministic hashing, so the same user always gets the same result.
96
+
97
+ ## Requirements
98
+
99
+ - Python 3.9+
100
+ - `boto3` (AWS SDK)
101
+ - AWS credentials with read access to your flag config bucket
102
+
103
+ ## Links
104
+
105
+ - [FlagDrop](https://flagdrop.io) — Feature flags in your cloud
106
+ - [Documentation](https://flagdrop.io/docs)
@@ -0,0 +1,79 @@
1
+ # FlagDrop Python SDK
2
+
3
+ Feature flag evaluation that runs entirely in your cloud. No vendor servers, no data leaving your infrastructure.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install flagdrop-sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ from flagdrop import FlagClient
15
+
16
+ client = FlagClient(
17
+ bucket="my-app-flags",
18
+ environment="production",
19
+ provider="aws",
20
+ region="us-east-1",
21
+ )
22
+ client.initialize()
23
+
24
+ # Boolean flag
25
+ enabled = client.get_bool("new-checkout", False)
26
+
27
+ # String flag with targeting context
28
+ theme = client.get_string("app-theme", "light", {"plan": "enterprise"})
29
+
30
+ # Number flag
31
+ max_items = client.get_number("max-items", 10)
32
+
33
+ # JSON flag
34
+ config = client.get_json("feature-config", {"limit": 5})
35
+ ```
36
+
37
+ ## How It Works
38
+
39
+ 1. You define flags in the [FlagDrop dashboard](https://flagdrop.io)
40
+ 2. FlagDrop pushes a JSON config file to an S3 bucket in **your** AWS account
41
+ 3. The SDK reads that file and evaluates flags locally at runtime
42
+
43
+ No network calls to FlagDrop servers during evaluation. No latency. No single point of failure.
44
+
45
+ ## Configuration
46
+
47
+ | Parameter | Description |
48
+ |-----------|-------------|
49
+ | `bucket` | S3 bucket name where flag configs are stored |
50
+ | `environment` | Environment name (e.g., `production`, `staging`) |
51
+ | `provider` | Cloud provider (`aws`) |
52
+ | `region` | AWS region (e.g., `us-east-1`) |
53
+ | `scope` | Config scope: `backend` (default) or `frontend` |
54
+ | `refresh_interval_seconds` | How often to re-fetch config (default: 30s, 0 to disable) |
55
+
56
+ ## Targeting Rules
57
+
58
+ The SDK evaluates targeting rules locally. Supported operators:
59
+
60
+ - `eq`, `neq` — exact match / not equal
61
+ - `in`, `notIn` — value in list / not in list
62
+ - `lt`, `gt` — less than / greater than (numeric)
63
+ - `startsWith`, `endsWith`, `contains` — string matching
64
+ - `segment` — segment membership
65
+
66
+ ## Rollouts
67
+
68
+ Percentage-based rollouts use deterministic hashing, so the same user always gets the same result.
69
+
70
+ ## Requirements
71
+
72
+ - Python 3.9+
73
+ - `boto3` (AWS SDK)
74
+ - AWS credentials with read access to your flag config bucket
75
+
76
+ ## Links
77
+
78
+ - [FlagDrop](https://flagdrop.io) — Feature flags in your cloud
79
+ - [Documentation](https://flagdrop.io/docs)
@@ -0,0 +1,10 @@
1
+ from flagdrop.client import FlagClient
2
+ from flagdrop.types import FlagClientConfig, UserContext, EvalResult, ConfigFile
3
+
4
+ __all__ = [
5
+ "FlagClient",
6
+ "FlagClientConfig",
7
+ "UserContext",
8
+ "EvalResult",
9
+ "ConfigFile",
10
+ ]
@@ -0,0 +1,150 @@
1
+ """FlagDrop Python SDK client."""
2
+ from __future__ import annotations
3
+
4
+ import time
5
+ from typing import Any, TypeVar
6
+
7
+ from flagdrop.evaluator import evaluate_flag
8
+ from flagdrop.providers import ConfigProvider, S3ConfigProvider
9
+ from flagdrop.types import ConfigFile, FlagClientConfig, UserContext
10
+
11
+ T = TypeVar("T")
12
+
13
+
14
+ class FlagClient:
15
+ """
16
+ FlagDrop feature flag client.
17
+
18
+ Fetches config from your cloud bucket and evaluates flags locally.
19
+
20
+ Usage::
21
+
22
+ from flagdrop import FlagClient
23
+
24
+ client = FlagClient(
25
+ bucket="my-app-flags",
26
+ environment="production",
27
+ provider="aws",
28
+ region="us-east-1",
29
+ )
30
+ client.initialize()
31
+
32
+ enabled = client.get_bool("new-checkout", False)
33
+ theme = client.get_string("app-theme", "light", {"user_id": "user-123"})
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ bucket: str,
39
+ environment: str,
40
+ provider: str,
41
+ region: str,
42
+ scope: str = "backend",
43
+ refresh_interval_seconds: float = 30,
44
+ config_provider: ConfigProvider | None = None,
45
+ ) -> None:
46
+ self._config = FlagClientConfig(
47
+ bucket=bucket,
48
+ environment=environment,
49
+ provider=provider, # type: ignore[arg-type]
50
+ region=region,
51
+ scope=scope,
52
+ refresh_interval_seconds=refresh_interval_seconds,
53
+ )
54
+ self._cache: ConfigFile | None = None
55
+ self._last_fetched_at: float = 0
56
+
57
+ if config_provider is not None:
58
+ self._provider = config_provider
59
+ else:
60
+ self._provider = self._create_provider()
61
+
62
+ def _create_provider(self) -> ConfigProvider:
63
+ if self._config.provider == "aws":
64
+ return S3ConfigProvider(self._config.region)
65
+ elif self._config.provider in ("gcp", "azure"):
66
+ raise ValueError(
67
+ f"Provider '{self._config.provider}' is not yet supported. Use 'aws'."
68
+ )
69
+ else:
70
+ raise ValueError(f"Unknown provider: '{self._config.provider}'")
71
+
72
+ def _config_key(self) -> str:
73
+ return f"flags-{self._config.environment}-{self._config.scope}.json"
74
+
75
+ def _is_stale(self) -> bool:
76
+ if self._cache is None:
77
+ return True
78
+ if self._config.refresh_interval_seconds <= 0:
79
+ return False
80
+ return (
81
+ time.time() - self._last_fetched_at
82
+ > self._config.refresh_interval_seconds
83
+ )
84
+
85
+ def _load_config(self) -> ConfigFile:
86
+ if self._cache is not None and not self._is_stale():
87
+ return self._cache
88
+ self._cache = self._provider.fetch(self._config.bucket, self._config_key())
89
+ self._last_fetched_at = time.time()
90
+ return self._cache
91
+
92
+ def initialize(self) -> None:
93
+ """Fetch config from the bucket. Call this before evaluating flags."""
94
+ self._load_config()
95
+
96
+ def get_config(self) -> ConfigFile | None:
97
+ """Return cached config, or None if not yet loaded."""
98
+ return self._cache
99
+
100
+ def get_bool(
101
+ self,
102
+ flag_key: str,
103
+ default_value: bool,
104
+ context: UserContext | None = None,
105
+ ) -> bool:
106
+ config = self._load_config()
107
+ result = evaluate_flag(config, flag_key, context)
108
+ if not result.found or not result.enabled:
109
+ return default_value
110
+ return result.value if isinstance(result.value, bool) else default_value
111
+
112
+ def get_string(
113
+ self,
114
+ flag_key: str,
115
+ default_value: str,
116
+ context: UserContext | None = None,
117
+ ) -> str:
118
+ config = self._load_config()
119
+ result = evaluate_flag(config, flag_key, context)
120
+ if not result.found or not result.enabled:
121
+ return default_value
122
+ return result.value if isinstance(result.value, str) else default_value
123
+
124
+ def get_number(
125
+ self,
126
+ flag_key: str,
127
+ default_value: float,
128
+ context: UserContext | None = None,
129
+ ) -> float:
130
+ config = self._load_config()
131
+ result = evaluate_flag(config, flag_key, context)
132
+ if not result.found or not result.enabled:
133
+ return default_value
134
+ return (
135
+ result.value
136
+ if isinstance(result.value, (int, float))
137
+ else default_value
138
+ )
139
+
140
+ def get_json(
141
+ self,
142
+ flag_key: str,
143
+ default_value: T,
144
+ context: UserContext | None = None,
145
+ ) -> T:
146
+ config = self._load_config()
147
+ result = evaluate_flag(config, flag_key, context)
148
+ if not result.found or not result.enabled:
149
+ return default_value
150
+ return result.value # type: ignore[return-value]
@@ -0,0 +1,113 @@
1
+ """Flag evaluation engine — mirrors @flagdrop/evaluator from the Node.js SDK."""
2
+ from __future__ import annotations
3
+
4
+ from flagdrop.types import ConfigFile, EvalResult, Rule, UserContext
5
+
6
+
7
+ def fnv1a_hash(input_str: str) -> int:
8
+ """FNV-1a 32-bit hash — deterministic bucketing."""
9
+ hash_val = 0x811C9DC5 # FNV offset basis
10
+ for ch in input_str:
11
+ hash_val ^= ord(ch)
12
+ hash_val = (hash_val * 0x01000193) & 0xFFFFFFFF # FNV prime, mask to 32 bits
13
+ return hash_val
14
+
15
+
16
+ def is_in_rollout(attribute_value: str, flag_key: str, percentage: float) -> bool:
17
+ """Deterministic percentage bucketing — same user always gets same result."""
18
+ if percentage <= 0:
19
+ return False
20
+ if percentage >= 100:
21
+ return True
22
+ hash_val = fnv1a_hash(flag_key + attribute_value)
23
+ bucket = hash_val % 100
24
+ return bucket < percentage
25
+
26
+
27
+ def match_rule(rule: Rule, context: UserContext) -> bool:
28
+ """Evaluate a single targeting rule against user context."""
29
+ # Segment operator — check context["segments"] array
30
+ if rule.operator == "segment":
31
+ segments = context.get("segments")
32
+ if isinstance(segments, list):
33
+ return any(str(s) == str(rule.value) for s in segments)
34
+ return False
35
+
36
+ context_value = context.get(rule.attribute)
37
+
38
+ # Missing attribute — only neq/notIn match on absence
39
+ if context_value is None:
40
+ return rule.operator in ("neq", "notIn")
41
+
42
+ op = rule.operator
43
+
44
+ if op == "eq":
45
+ return str(context_value) == str(rule.value)
46
+ elif op == "neq":
47
+ return str(context_value) != str(rule.value)
48
+ elif op == "in":
49
+ values = rule.value if isinstance(rule.value, list) else []
50
+ return any(str(v) == str(context_value) for v in values)
51
+ elif op == "notIn":
52
+ values = rule.value if isinstance(rule.value, list) else []
53
+ return not any(str(v) == str(context_value) for v in values)
54
+ elif op == "lt":
55
+ try:
56
+ return float(context_value) < float(rule.value)
57
+ except (ValueError, TypeError):
58
+ return False
59
+ elif op == "gt":
60
+ try:
61
+ return float(context_value) > float(rule.value)
62
+ except (ValueError, TypeError):
63
+ return False
64
+ elif op == "startsWith":
65
+ return str(context_value).startswith(str(rule.value))
66
+ elif op == "endsWith":
67
+ return str(context_value).endswith(str(rule.value))
68
+ elif op == "contains":
69
+ return str(rule.value) in str(context_value)
70
+ else:
71
+ return False
72
+
73
+
74
+ def evaluate_flag(
75
+ config: ConfigFile, flag_key: str, context: UserContext | None = None
76
+ ) -> EvalResult:
77
+ """
78
+ Evaluate a flag from a config file against optional user context.
79
+
80
+ Evaluation order:
81
+ 1. Look up flag — if not found, return found=False
82
+ 2. If disabled, return enabled=False with flag default
83
+ 3. Rules evaluated in order — first match wins
84
+ 4. Rollout check — deterministic hash bucketing
85
+ 5. No match → return flag default with enabled=True
86
+ """
87
+ flag = config.flags.get(flag_key)
88
+
89
+ if flag is None:
90
+ return EvalResult(found=False, enabled=False, value=None)
91
+
92
+ if not flag.enabled:
93
+ return EvalResult(found=True, enabled=False, value=flag.default_value)
94
+
95
+ # Rules — first match wins
96
+ if flag.rules and context:
97
+ for rule in flag.rules:
98
+ if match_rule(rule, context):
99
+ return EvalResult(
100
+ found=True,
101
+ enabled=True,
102
+ value=rule.serve_value,
103
+ rule_match=rule.attribute,
104
+ )
105
+
106
+ # Rollout — deterministic percentage bucketing
107
+ if flag.rollout and context:
108
+ attr_value = context.get(flag.rollout.attribute)
109
+ if attr_value is not None:
110
+ if not is_in_rollout(str(attr_value), flag_key, flag.rollout.percentage):
111
+ return EvalResult(found=True, enabled=False, value=flag.default_value)
112
+
113
+ return EvalResult(found=True, enabled=True, value=flag.default_value)
@@ -0,0 +1,35 @@
1
+ """Cloud storage providers for fetching flag config files."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ from typing import Protocol
6
+
7
+ from flagdrop.types import ConfigFile
8
+
9
+
10
+ class ConfigProvider(Protocol):
11
+ """Interface for fetching config from cloud storage."""
12
+
13
+ def fetch(self, bucket: str, key: str) -> ConfigFile: ...
14
+
15
+
16
+ class S3ConfigProvider:
17
+ """Fetches flag config from AWS S3."""
18
+
19
+ def __init__(self, region: str) -> None:
20
+ self._region = region
21
+ self._client = None
22
+
23
+ def _get_client(self):
24
+ if self._client is None:
25
+ import boto3
26
+
27
+ self._client = boto3.client("s3", region_name=self._region)
28
+ return self._client
29
+
30
+ def fetch(self, bucket: str, key: str) -> ConfigFile:
31
+ client = self._get_client()
32
+ resp = client.get_object(Bucket=bucket, Key=key)
33
+ body = resp["Body"].read().decode("utf-8")
34
+ data = json.loads(body)
35
+ return ConfigFile.from_dict(data)
@@ -0,0 +1,93 @@
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass, field
3
+ from typing import Any, Literal
4
+
5
+
6
+ UserContext = dict[str, Any]
7
+
8
+
9
+ @dataclass
10
+ class Rule:
11
+ attribute: str
12
+ operator: str # eq, neq, in, notIn, lt, gt, startsWith, endsWith, contains, segment
13
+ value: Any
14
+ serve_value: Any
15
+
16
+ @classmethod
17
+ def from_dict(cls, d: dict[str, Any]) -> Rule:
18
+ return cls(
19
+ attribute=d.get("attribute", ""),
20
+ operator=d.get("operator", ""),
21
+ value=d.get("value"),
22
+ serve_value=d.get("serveValue"),
23
+ )
24
+
25
+
26
+ @dataclass
27
+ class Rollout:
28
+ percentage: float
29
+ attribute: str
30
+
31
+ @classmethod
32
+ def from_dict(cls, d: dict[str, Any]) -> Rollout:
33
+ return cls(percentage=d.get("percentage", 0), attribute=d.get("attribute", ""))
34
+
35
+
36
+ @dataclass
37
+ class ConfigFlag:
38
+ type: str # boolean, string, number, json
39
+ enabled: bool
40
+ default_value: Any
41
+ rules: list[Rule] = field(default_factory=list)
42
+ rollout: Rollout | None = None
43
+ description: str = ""
44
+
45
+ @classmethod
46
+ def from_dict(cls, d: dict[str, Any]) -> ConfigFlag:
47
+ rules = [Rule.from_dict(r) for r in (d.get("rules") or [])]
48
+ rollout_data = d.get("rollout")
49
+ rollout = Rollout.from_dict(rollout_data) if rollout_data else None
50
+ return cls(
51
+ type=d.get("type", "boolean"),
52
+ enabled=d.get("enabled", False),
53
+ default_value=d.get("defaultValue"),
54
+ rules=rules,
55
+ rollout=rollout,
56
+ description=d.get("description", ""),
57
+ )
58
+
59
+
60
+ @dataclass
61
+ class ConfigFile:
62
+ version: int
63
+ updated_at: str
64
+ scope: str
65
+ flags: dict[str, ConfigFlag]
66
+
67
+ @classmethod
68
+ def from_dict(cls, d: dict[str, Any]) -> ConfigFile:
69
+ flags = {k: ConfigFlag.from_dict(v) for k, v in (d.get("flags") or {}).items()}
70
+ return cls(
71
+ version=d.get("version", 0),
72
+ updated_at=d.get("updatedAt", ""),
73
+ scope=d.get("scope", ""),
74
+ flags=flags,
75
+ )
76
+
77
+
78
+ @dataclass
79
+ class EvalResult:
80
+ found: bool
81
+ enabled: bool
82
+ value: Any
83
+ rule_match: str | None = None
84
+
85
+
86
+ @dataclass
87
+ class FlagClientConfig:
88
+ bucket: str
89
+ environment: str
90
+ provider: Literal["aws", "gcp", "azure"]
91
+ region: str
92
+ scope: str = "backend"
93
+ refresh_interval_seconds: float = 30
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: flagdrop-sdk
3
+ Version: 0.1.0
4
+ Summary: FlagDrop Python SDK — evaluate feature flags from your cloud bucket
5
+ Author-email: FlagDrop <support@flagdrop.io>
6
+ License: MIT
7
+ Project-URL: Homepage, https://flagdrop.io
8
+ Project-URL: Documentation, https://flagdrop.io/docs
9
+ Keywords: feature-flags,flagdrop,sdk,s3,feature-toggles
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: boto3>=1.28.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=7.0; extra == "dev"
25
+ Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
26
+ Requires-Dist: moto[s3]>=5.0; extra == "dev"
27
+
28
+ # FlagDrop Python SDK
29
+
30
+ Feature flag evaluation that runs entirely in your cloud. No vendor servers, no data leaving your infrastructure.
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install flagdrop-sdk
36
+ ```
37
+
38
+ ## Quick Start
39
+
40
+ ```python
41
+ from flagdrop import FlagClient
42
+
43
+ client = FlagClient(
44
+ bucket="my-app-flags",
45
+ environment="production",
46
+ provider="aws",
47
+ region="us-east-1",
48
+ )
49
+ client.initialize()
50
+
51
+ # Boolean flag
52
+ enabled = client.get_bool("new-checkout", False)
53
+
54
+ # String flag with targeting context
55
+ theme = client.get_string("app-theme", "light", {"plan": "enterprise"})
56
+
57
+ # Number flag
58
+ max_items = client.get_number("max-items", 10)
59
+
60
+ # JSON flag
61
+ config = client.get_json("feature-config", {"limit": 5})
62
+ ```
63
+
64
+ ## How It Works
65
+
66
+ 1. You define flags in the [FlagDrop dashboard](https://flagdrop.io)
67
+ 2. FlagDrop pushes a JSON config file to an S3 bucket in **your** AWS account
68
+ 3. The SDK reads that file and evaluates flags locally at runtime
69
+
70
+ No network calls to FlagDrop servers during evaluation. No latency. No single point of failure.
71
+
72
+ ## Configuration
73
+
74
+ | Parameter | Description |
75
+ |-----------|-------------|
76
+ | `bucket` | S3 bucket name where flag configs are stored |
77
+ | `environment` | Environment name (e.g., `production`, `staging`) |
78
+ | `provider` | Cloud provider (`aws`) |
79
+ | `region` | AWS region (e.g., `us-east-1`) |
80
+ | `scope` | Config scope: `backend` (default) or `frontend` |
81
+ | `refresh_interval_seconds` | How often to re-fetch config (default: 30s, 0 to disable) |
82
+
83
+ ## Targeting Rules
84
+
85
+ The SDK evaluates targeting rules locally. Supported operators:
86
+
87
+ - `eq`, `neq` — exact match / not equal
88
+ - `in`, `notIn` — value in list / not in list
89
+ - `lt`, `gt` — less than / greater than (numeric)
90
+ - `startsWith`, `endsWith`, `contains` — string matching
91
+ - `segment` — segment membership
92
+
93
+ ## Rollouts
94
+
95
+ Percentage-based rollouts use deterministic hashing, so the same user always gets the same result.
96
+
97
+ ## Requirements
98
+
99
+ - Python 3.9+
100
+ - `boto3` (AWS SDK)
101
+ - AWS credentials with read access to your flag config bucket
102
+
103
+ ## Links
104
+
105
+ - [FlagDrop](https://flagdrop.io) — Feature flags in your cloud
106
+ - [Documentation](https://flagdrop.io/docs)