flagpool-sdk 0.3.1__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,458 @@
1
+ Metadata-Version: 2.4
2
+ Name: flagpool-sdk
3
+ Version: 0.3.1
4
+ Summary: Python SDK for Flagpool feature flags
5
+ License: MIT
6
+ Project-URL: Homepage, https://flagpool.io
7
+ Project-URL: Documentation, https://flagpool.io/docs
8
+ Project-URL: Repository, https://github.com/flagpool/flagpool-sdk
9
+ Keywords: feature-flags,feature-toggles,flagpool,sdk
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.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: requests>=2.25.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
25
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
26
+ Requires-Dist: responses>=0.23.0; extra == "dev"
27
+
28
+ # Flagpool SDK for Python
29
+
30
+ Official Python SDK for [Flagpool](https://flagpool.io) - the modern feature flag platform for teams who want control without complexity.
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install flagpool-sdk
36
+ ```
37
+
38
+ ## Quick Start
39
+
40
+ ```python
41
+ from flagpool_sdk import FlagpoolClient
42
+
43
+ client = FlagpoolClient(
44
+ project_id='your-project-uuid', # Get from Flagpool dashboard
45
+ api_key='fp_production_xxx', # Environment-specific API key
46
+ decryption_key='fp_dec_xxx', # For CDN URL hash & target lists
47
+ context={
48
+ 'userId': 'user-123',
49
+ 'email': 'alice@example.com',
50
+ 'plan': 'pro',
51
+ 'country': 'US'
52
+ }
53
+ )
54
+
55
+ client.init()
56
+
57
+ # Boolean flag
58
+ if client.is_enabled('new-dashboard'):
59
+ show_new_dashboard()
60
+
61
+ # String flag (A/B test)
62
+ button_color = client.get_value('cta-button-color')
63
+ # 'blue' | 'green' | 'orange'
64
+
65
+ # Number flag
66
+ max_upload = client.get_value('max-upload-size-mb')
67
+ # 10 | 100 | 1000
68
+
69
+ # JSON flag
70
+ config = client.get_value('checkout-config')
71
+ # { 'showCoupons': True, 'maxItems': 50, ... }
72
+
73
+ # Clean up when done
74
+ client.close()
75
+ ```
76
+
77
+ ## Features
78
+
79
+ - ✅ **Local evaluation** - No server roundtrip per flag check
80
+ - ✅ **Deterministic rollouts** - Same user always gets same variation
81
+ - ✅ **Multiple flag types** - Boolean, string, number, JSON (dict)
82
+ - ✅ **Advanced targeting** - 8 operators including target lists
83
+ - ✅ **Real-time updates** - Automatic polling for flag changes
84
+ - ✅ **Offline support** - Works with cached flags when offline
85
+ - ✅ **Minimal dependencies** - Only requires `requests`
86
+
87
+ ## Configuration
88
+
89
+ ```python
90
+ client = FlagpoolClient(
91
+ # Required
92
+ project_id='your-project-uuid', # Project ID from dashboard
93
+ api_key='fp_production_xxx', # Environment-specific API key
94
+ decryption_key='fp_dec_xxx', # For CDN URL hash & target list decryption
95
+
96
+ # Optional
97
+ context={ # User context for targeting
98
+ 'userId': 'user-123',
99
+ 'email': 'user@example.com',
100
+ 'plan': 'pro',
101
+ # Add any attributes for targeting
102
+ },
103
+ polling_interval=30, # Auto-refresh interval (seconds), default: 30
104
+ url_override=None, # Complete URL override (for self-hosted/testing)
105
+ )
106
+ ```
107
+
108
+ ## API Reference
109
+
110
+ ### Methods
111
+
112
+ | Method | Description |
113
+ |--------|-------------|
114
+ | `init()` | Initialize client and fetch flags (required before evaluation) |
115
+ | `is_enabled(key)` | Check if a boolean flag is enabled |
116
+ | `get_value(key)` | Get flag value (any type) |
117
+ | `get_variation(key)` | Alias for get_value |
118
+ | `get_all_flags()` | Get all evaluated flag values as dict |
119
+ | `update_context(ctx)` | Update user context and re-evaluate flags |
120
+ | `on_change(callback)` | Subscribe to flag value changes |
121
+ | `close()` | Clean up resources (stop polling) |
122
+
123
+ ### Flag Types
124
+
125
+ #### Boolean Flags
126
+
127
+ ```python
128
+ if client.is_enabled('feature-flag'):
129
+ # Feature is enabled for this user
130
+ pass
131
+ ```
132
+
133
+ #### String Flags
134
+
135
+ Perfect for A/B tests and feature variants:
136
+
137
+ ```python
138
+ variant = client.get_value('button-color')
139
+ # Returns: 'blue' | 'green' | 'orange'
140
+ ```
141
+
142
+ #### Number Flags
143
+
144
+ Great for limits, thresholds, and configurations:
145
+
146
+ ```python
147
+ limit = client.get_value('rate-limit')
148
+ # Returns: 100 | 1000 | 10000
149
+ ```
150
+
151
+ #### JSON Flags (dict)
152
+
153
+ For complex configurations:
154
+
155
+ ```python
156
+ config = client.get_value('checkout-config')
157
+ # Returns: { 'showCoupons': True, 'maxItems': 50, ... }
158
+ ```
159
+
160
+ ## Targeting Rules
161
+
162
+ Flagpool supports powerful targeting with 8 operators:
163
+
164
+ | Operator | Description | Example |
165
+ |----------|-------------|---------|
166
+ | `eq` | Equals | `plan == "enterprise"` |
167
+ | `neq` | Not equals | `plan != "free"` |
168
+ | `in` | In list | `country in ["US", "CA"]` |
169
+ | `nin` | Not in list | `country not in ["CN", "RU"]` |
170
+ | `contains` | String contains | `email contains "@company.com"` |
171
+ | `startsWith` | String starts with | `userId startsWith "admin-"` |
172
+ | `inTargetList` | In target list | `userId in beta-testers` |
173
+ | `notInTargetList` | Not in target list | `userId not in blocked-users` |
174
+
175
+ ## Dynamic Context Updates
176
+
177
+ Update user context on the fly - flags re-evaluate automatically:
178
+
179
+ ```python
180
+ client = FlagpoolClient(
181
+ api_key='fp_prod_xxx',
182
+ environment='production',
183
+ context={'userId': 'user-1', 'plan': 'free'}
184
+ )
185
+
186
+ client.init()
187
+
188
+ # User on free plan
189
+ print(client.get_value('max-upload-size-mb')) # 10
190
+
191
+ # User upgrades to pro
192
+ client.update_context({'plan': 'pro'})
193
+
194
+ # Instantly gets pro limits
195
+ print(client.get_value('max-upload-size-mb')) # 100
196
+ ```
197
+
198
+ ## Real-time Updates
199
+
200
+ Flags automatically refresh in the background:
201
+
202
+ ```python
203
+ client = FlagpoolClient(
204
+ api_key='fp_prod_xxx',
205
+ environment='production',
206
+ context={'userId': 'user-1'},
207
+ polling_interval=30 # Refresh every 30 seconds
208
+ )
209
+
210
+ client.init()
211
+
212
+ # Listen for flag changes
213
+ def on_flag_change(flag_key, new_value):
214
+ print(f'Flag {flag_key} changed to: {new_value}')
215
+
216
+ # React to changes
217
+ if flag_key == 'maintenance-mode' and new_value:
218
+ show_maintenance_banner()
219
+
220
+ client.on_change(on_flag_change)
221
+ ```
222
+
223
+ ## Framework Examples
224
+
225
+ ### Django
226
+
227
+ ```python
228
+ # settings.py
229
+ FLAGPOOL_API_KEY = 'fp_production_xxx'
230
+
231
+ # flags.py
232
+ from flagpool_sdk import FlagpoolClient
233
+ from django.conf import settings
234
+
235
+ _client = None
236
+
237
+ def get_flagpool():
238
+ global _client
239
+ if _client is None:
240
+ _client = FlagpoolClient(
241
+ api_key=settings.FLAGPOOL_API_KEY,
242
+ environment='production',
243
+ )
244
+ _client.init()
245
+ return _client
246
+
247
+ # views.py
248
+ from .flags import get_flagpool
249
+
250
+ def my_view(request):
251
+ flagpool = get_flagpool()
252
+ flagpool.update_context({'userId': request.user.id})
253
+
254
+ if flagpool.is_enabled('new-checkout'):
255
+ return render(request, 'new_checkout.html')
256
+ return render(request, 'checkout.html')
257
+ ```
258
+
259
+ ### Flask
260
+
261
+ ```python
262
+ from flask import Flask, g
263
+ from flagpool_sdk import FlagpoolClient
264
+
265
+ app = Flask(__name__)
266
+
267
+ flagpool = FlagpoolClient(
268
+ api_key='fp_production_xxx',
269
+ environment='production',
270
+ )
271
+ flagpool.init()
272
+
273
+ @app.before_request
274
+ def set_user_context():
275
+ if hasattr(g, 'user'):
276
+ flagpool.update_context({'userId': g.user.id})
277
+
278
+ @app.route('/dashboard')
279
+ def dashboard():
280
+ if flagpool.is_enabled('new-dashboard'):
281
+ return render_template('new_dashboard.html')
282
+ return render_template('dashboard.html')
283
+ ```
284
+
285
+ ### FastAPI
286
+
287
+ ```python
288
+ from fastapi import FastAPI, Depends
289
+ from flagpool_sdk import FlagpoolClient
290
+
291
+ app = FastAPI()
292
+
293
+ flagpool = FlagpoolClient(
294
+ api_key='fp_production_xxx',
295
+ environment='production',
296
+ )
297
+
298
+ @app.on_event("startup")
299
+ async def startup():
300
+ flagpool.init()
301
+
302
+ @app.on_event("shutdown")
303
+ async def shutdown():
304
+ flagpool.close()
305
+
306
+ @app.get("/api/data")
307
+ async def get_data(user_id: str):
308
+ flagpool.update_context({'userId': user_id})
309
+
310
+ if flagpool.is_enabled('new-api-response'):
311
+ return {"version": "v2", "data": new_data}
312
+ return {"version": "v1", "data": legacy_data}
313
+ ```
314
+
315
+ ## Target List Encryption
316
+
317
+ By default, target lists (used for `inTargetList` and `notInTargetList` operators) are encrypted in SDK exports to protect sensitive user data like emails and user IDs.
318
+
319
+ ### Encrypted Target Lists (Default)
320
+
321
+ If your environment has target list encryption enabled, you must provide a crypto adapter:
322
+
323
+ ```python
324
+ from flagpool_sdk import FlagpoolClient, set_crypto_adapter
325
+
326
+ class MyCryptoAdapter:
327
+ def decrypt(self, ciphertext: str, key: str, iv: str, tag: str) -> str:
328
+ # Implement AES-256-GCM decryption
329
+ # See examples/ for a complete implementation using cryptography library
330
+ return decrypted_text
331
+
332
+ # Set the adapter before creating the client
333
+ set_crypto_adapter(MyCryptoAdapter())
334
+
335
+ client = FlagpoolClient(
336
+ project_id='your-project-uuid',
337
+ api_key='fp_production_xxx',
338
+ decryption_key='fp_dec_xxx',
339
+ context={'userId': 'user-123'}
340
+ )
341
+ ```
342
+
343
+ ### Error: "Encrypted target lists received but no crypto adapter configured"
344
+
345
+ If you see this error, it means:
346
+
347
+ 1. **Your environment has target list encryption enabled** (the default)
348
+ 2. **You haven't set up a crypto adapter**
349
+
350
+ **Solutions:**
351
+
352
+ 1. **Set up a crypto adapter** (recommended for production):
353
+ ```python
354
+ set_crypto_adapter(MyCryptoAdapter())
355
+ ```
356
+
357
+ 2. **Disable encryption** (for development/testing):
358
+ - Go to Flagpool Dashboard → Settings → Environments
359
+ - Toggle off "Target List Encryption" for your environment
360
+ - This will export target lists in plaintext (values will be visible)
361
+
362
+ ### Plaintext Target Lists
363
+
364
+ If you disable target list encryption in the dashboard, no crypto adapter is needed. The SDK will use target lists directly without decryption.
365
+
366
+ > ⚠️ **Security Note:** Disabling encryption exposes target list values (emails, user IDs, etc.) in plaintext in the SDK exports. Only disable for development environments or non-sensitive data.
367
+
368
+ ## Analytics (Paid Plans Only)
369
+
370
+ Track flag evaluation counts to understand usage patterns. Analytics is **opt-in**, disabled by default, and only available on paid plans.
371
+
372
+ ### Enabling Analytics
373
+
374
+ ```python
375
+ from flagpool_sdk import FlagpoolClient, AnalyticsConfig
376
+
377
+ client = FlagpoolClient(
378
+ project_id="your-project-uuid",
379
+ api_key="your-api-key",
380
+ decryption_key="your-decryption-key",
381
+ analytics=AnalyticsConfig(enabled=True),
382
+ )
383
+ client.init()
384
+ ```
385
+
386
+ ### Configuration Options
387
+
388
+ ```python
389
+ analytics=AnalyticsConfig(
390
+ enabled=True,
391
+ flush_interval=60, # Flush interval in seconds (min: 30)
392
+ flush_threshold=100, # Flush after N evaluations
393
+ sample_rate=1.0, # Sample rate 0.0–1.0
394
+ sync_flush_on_shutdown=False, # Block on exit until flushed
395
+ )
396
+ ```
397
+
398
+ | Option | Type | Default | Description |
399
+ |--------|------|---------|-------------|
400
+ | `enabled` | bool | `False` | Enable/disable analytics |
401
+ | `flush_interval` | int | `60` | Flush interval in seconds (minimum: 30) |
402
+ | `flush_threshold` | int | `100` | Flush after N evaluations |
403
+ | `sample_rate` | float | `1.0` | Sample rate (0.0–1.0) |
404
+ | `sync_flush_on_shutdown` | bool | `False` | Flush synchronously on process exit |
405
+
406
+ ### How It Works
407
+
408
+ - Evaluation counts are batched in memory
409
+ - Batches are sent asynchronously (fire-and-forget)
410
+ - Analytics never blocks flag evaluation
411
+ - Data is aggregated daily in your Flagpool dashboard
412
+ - **Note**: Data is silently discarded for free plan projects
413
+
414
+ ### Serverless / Short-Lived Processes
415
+
416
+ For AWS Lambda or similar short-lived environments, lower the threshold and enable sync flush:
417
+
418
+ ```python
419
+ analytics=AnalyticsConfig(
420
+ enabled=True,
421
+ flush_threshold=10,
422
+ sync_flush_on_shutdown=True,
423
+ )
424
+ ```
425
+
426
+ ### Debugging Analytics
427
+
428
+ ```python
429
+ # Get current analytics state
430
+ state = client.get_analytics_state()
431
+ print(state.buffer) # {'my-flag': 5}
432
+ print(state.buffer_size) # 5
433
+ print(state.total_flushes) # 2
434
+
435
+ # Get all flags with evaluation status
436
+ flags = client.get_all_flags_with_state()
437
+ # {'my-flag': {'value': True, 'evaluated': True}, ...}
438
+
439
+ # Manually flush analytics buffer
440
+ client.flush_analytics()
441
+ ```
442
+
443
+ ## Documentation
444
+
445
+ For complete documentation, guides, and best practices, visit:
446
+
447
+ 📚 **[flagpool.io/docs](https://flagpool.io/docs)**
448
+
449
+ ## Support
450
+
451
+ - 📖 [Documentation](https://flagpool.io/docs)
452
+ - 🐛 [Report Issues](https://github.com/flagpool/flagpool-sdk/issues)
453
+ - ✉️ [Email Support](mailto:support@flagpool.io)
454
+
455
+ ## License
456
+
457
+ MIT © [Flagpool](https://flagpool.io)
458
+