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.
- flagpool_sdk-0.3.1/PKG-INFO +458 -0
- flagpool_sdk-0.3.1/README.md +431 -0
- flagpool_sdk-0.3.1/flagpool_sdk/__init__.py +44 -0
- flagpool_sdk-0.3.1/flagpool_sdk/analytics.py +281 -0
- flagpool_sdk-0.3.1/flagpool_sdk/client.py +302 -0
- flagpool_sdk-0.3.1/flagpool_sdk/crypto.py +124 -0
- flagpool_sdk-0.3.1/flagpool_sdk/evaluator.py +172 -0
- flagpool_sdk-0.3.1/flagpool_sdk/fetcher.py +67 -0
- flagpool_sdk-0.3.1/flagpool_sdk/hash.py +47 -0
- flagpool_sdk-0.3.1/flagpool_sdk/types.py +50 -0
- flagpool_sdk-0.3.1/flagpool_sdk/utils.py +57 -0
- flagpool_sdk-0.3.1/flagpool_sdk.egg-info/PKG-INFO +458 -0
- flagpool_sdk-0.3.1/flagpool_sdk.egg-info/SOURCES.txt +24 -0
- flagpool_sdk-0.3.1/flagpool_sdk.egg-info/dependency_links.txt +1 -0
- flagpool_sdk-0.3.1/flagpool_sdk.egg-info/requires.txt +6 -0
- flagpool_sdk-0.3.1/flagpool_sdk.egg-info/top_level.txt +1 -0
- flagpool_sdk-0.3.1/pyproject.toml +49 -0
- flagpool_sdk-0.3.1/setup.cfg +4 -0
- flagpool_sdk-0.3.1/tests/test_analytics.py +285 -0
- flagpool_sdk-0.3.1/tests/test_conformance.py +189 -0
- flagpool_sdk-0.3.1/tests/test_encrypted_target_lists.py +87 -0
- flagpool_sdk-0.3.1/tests/test_evaluator.py +608 -0
- flagpool_sdk-0.3.1/tests/test_hash.py +93 -0
- flagpool_sdk-0.3.1/tests/test_missing_flags.py +127 -0
- flagpool_sdk-0.3.1/tests/test_polling.py +237 -0
- flagpool_sdk-0.3.1/tests/test_utils.py +88 -0
|
@@ -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
|
+
|