flagkit 1.0.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.
Files changed (38) hide show
  1. flagkit-1.0.0/.gitignore +75 -0
  2. flagkit-1.0.0/PKG-INFO +585 -0
  3. flagkit-1.0.0/README.md +551 -0
  4. flagkit-1.0.0/pyproject.toml +122 -0
  5. flagkit-1.0.0/src/flagkit/__init__.py +66 -0
  6. flagkit-1.0.0/src/flagkit/client.py +756 -0
  7. flagkit-1.0.0/src/flagkit/core/__init__.py +35 -0
  8. flagkit-1.0.0/src/flagkit/core/cache.py +333 -0
  9. flagkit-1.0.0/src/flagkit/core/context_manager.py +122 -0
  10. flagkit-1.0.0/src/flagkit/core/event_persistence.py +621 -0
  11. flagkit-1.0.0/src/flagkit/core/event_queue.py +417 -0
  12. flagkit-1.0.0/src/flagkit/core/polling_manager.py +190 -0
  13. flagkit-1.0.0/src/flagkit/core/streaming_manager.py +530 -0
  14. flagkit-1.0.0/src/flagkit/errors/__init__.py +52 -0
  15. flagkit-1.0.0/src/flagkit/errors/error_codes.py +527 -0
  16. flagkit-1.0.0/src/flagkit/errors/flagkit_error.py +288 -0
  17. flagkit-1.0.0/src/flagkit/errors/sanitizer.py +98 -0
  18. flagkit-1.0.0/src/flagkit/flagkit.py +278 -0
  19. flagkit-1.0.0/src/flagkit/http/__init__.py +44 -0
  20. flagkit-1.0.0/src/flagkit/http/circuit_breaker.py +191 -0
  21. flagkit-1.0.0/src/flagkit/http/http_client.py +552 -0
  22. flagkit-1.0.0/src/flagkit/http/retry.py +254 -0
  23. flagkit-1.0.0/src/flagkit/py.typed +0 -0
  24. flagkit-1.0.0/src/flagkit/storage/__init__.py +11 -0
  25. flagkit-1.0.0/src/flagkit/storage/encrypted_storage.py +322 -0
  26. flagkit-1.0.0/src/flagkit/storage/memory_storage.py +57 -0
  27. flagkit-1.0.0/src/flagkit/storage/storage.py +45 -0
  28. flagkit-1.0.0/src/flagkit/types/__init__.py +51 -0
  29. flagkit-1.0.0/src/flagkit/types/config.py +198 -0
  30. flagkit-1.0.0/src/flagkit/types/context.py +154 -0
  31. flagkit-1.0.0/src/flagkit/types/events.py +101 -0
  32. flagkit-1.0.0/src/flagkit/types/flag.py +192 -0
  33. flagkit-1.0.0/src/flagkit/utils/__init__.py +86 -0
  34. flagkit-1.0.0/src/flagkit/utils/logger.py +120 -0
  35. flagkit-1.0.0/src/flagkit/utils/platform.py +67 -0
  36. flagkit-1.0.0/src/flagkit/utils/security.py +548 -0
  37. flagkit-1.0.0/src/flagkit/utils/validators.py +193 -0
  38. flagkit-1.0.0/src/flagkit/utils/version.py +126 -0
@@ -0,0 +1,75 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ *.egg-info/
24
+ .installed.cfg
25
+ *.egg
26
+
27
+ # PyInstaller
28
+ *.manifest
29
+ *.spec
30
+
31
+ # Installer logs
32
+ pip-log.txt
33
+ pip-delete-this-directory.txt
34
+
35
+ # Unit test / coverage reports
36
+ htmlcov/
37
+ .tox/
38
+ .nox/
39
+ .coverage
40
+ .coverage.*
41
+ .cache
42
+ nosetests.xml
43
+ coverage.xml
44
+ *.cover
45
+ *.py,cover
46
+ .hypothesis/
47
+ .pytest_cache/
48
+
49
+ # Translations
50
+ *.mo
51
+ *.pot
52
+
53
+ # Environments
54
+ .env
55
+ .venv
56
+ env/
57
+ venv/
58
+ ENV/
59
+ env.bak/
60
+ venv.bak/
61
+
62
+ # mypy
63
+ .mypy_cache/
64
+ .dmypy.json
65
+ dmypy.json
66
+
67
+ # IDEs
68
+ .idea/
69
+ .vscode/
70
+ *.swp
71
+ *.swo
72
+
73
+ # OS
74
+ .DS_Store
75
+ Thumbs.db
flagkit-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,585 @@
1
+ Metadata-Version: 2.4
2
+ Name: flagkit
3
+ Version: 1.0.0
4
+ Summary: FlagKit SDK for Python - Feature flag evaluation and analytics
5
+ Project-URL: Homepage, https://flagkit.dev
6
+ Project-URL: Documentation, https://docs.flagkit.dev
7
+ Project-URL: Repository, https://github.com/teracrafts/flagkit-sdk
8
+ Project-URL: Issues, https://github.com/teracrafts/flagkit-sdk/issues
9
+ Author-email: FlagKit <sdk@flagkit.dev>
10
+ License-Expression: MIT
11
+ Keywords: feature-flags,feature-toggles,flagkit,sdk
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.9
24
+ Requires-Dist: cryptography>=41.0.0
25
+ Requires-Dist: httpx>=0.25.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: mypy>=1.8.0; extra == 'dev'
28
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
29
+ Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
30
+ Requires-Dist: pytest-httpx>=0.28.0; extra == 'dev'
31
+ Requires-Dist: pytest>=7.4.0; extra == 'dev'
32
+ Requires-Dist: ruff>=0.1.9; extra == 'dev'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # FlagKit Python SDK
36
+
37
+ Official Python SDK for [FlagKit](https://flagkit.dev) - Feature flag management made simple.
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install flagkit
43
+ ```
44
+
45
+ ## Requirements
46
+
47
+ - Python 3.9+
48
+ - `httpx` (automatically installed)
49
+ - `cryptography` (automatically installed, for cache encryption)
50
+
51
+ ## Quick Start
52
+
53
+ ```python
54
+ from flagkit import FlagKit
55
+
56
+ # Initialize the SDK
57
+ client = FlagKit.initialize(
58
+ api_key="sdk_your_api_key",
59
+ polling_interval=30, # seconds
60
+ cache_ttl=300, # seconds
61
+ )
62
+
63
+ # Wait for initialization
64
+ client.wait_for_ready()
65
+
66
+ # Identify a user
67
+ client.identify("user-123", {"plan": "premium"})
68
+
69
+ # Evaluate flags
70
+ dark_mode = client.get_boolean_value("dark-mode", default=False)
71
+ welcome_message = client.get_string_value("welcome-message", default="Hello!")
72
+ max_items = client.get_number_value("max-items", default=10)
73
+ config = client.get_json_value("feature-config", default={})
74
+
75
+ # Get full evaluation details
76
+ result = client.evaluate("dark-mode")
77
+ print(f"Value: {result.value}, Reason: {result.reason}")
78
+
79
+ # Track custom events
80
+ client.track("button_clicked", {"button": "signup"})
81
+
82
+ # Cleanup when done
83
+ client.close()
84
+ ```
85
+
86
+ ## Features
87
+
88
+ - **Type-safe evaluation** - Boolean, string, number, and JSON flag types
89
+ - **Local caching** - Fast evaluations with configurable TTL and optional encryption
90
+ - **Background polling** - Automatic flag updates with jitter
91
+ - **Event tracking** - Analytics with batching and crash-resilient persistence
92
+ - **Resilient** - Circuit breaker, retry with exponential backoff, offline support
93
+ - **Thread-safe** - Safe for concurrent use
94
+ - **Security** - PII detection, request signing, bootstrap verification, timing attack protection
95
+
96
+ ## Architecture
97
+
98
+ The SDK is organized into clean, modular packages:
99
+
100
+ ```
101
+ flagkit/
102
+ ├── __init__.py # Public exports
103
+ ├── flagkit.py # Static FlagKit factory (singleton)
104
+ ├── client.py # FlagKitClient implementation
105
+ ├── types/ # Public type definitions
106
+ │ ├── config.py # FlagKitOptions, BootstrapConfig
107
+ │ ├── context.py # EvaluationContext
108
+ │ ├── flag.py # FlagState, EvaluationResult
109
+ │ └── events.py # Event types
110
+ ├── errors/ # Error types and codes
111
+ │ ├── error_codes.py # 31 error codes
112
+ │ ├── flagkit_error.py # Error hierarchy
113
+ │ └── sanitizer.py # Error message sanitization
114
+ ├── http/ # HTTP client, circuit breaker, retry
115
+ │ ├── http_client.py # HTTP with retry/circuit integration
116
+ │ ├── circuit_breaker.py
117
+ │ └── retry.py # Exponential backoff with jitter
118
+ ├── core/ # Core components
119
+ │ ├── cache.py # In-memory cache with TTL
120
+ │ ├── context_manager.py
121
+ │ ├── polling_manager.py # Background polling
122
+ │ ├── event_queue.py # Event batching
123
+ │ └── event_persistence.py # Crash-resilient persistence
124
+ ├── storage/ # Storage implementations
125
+ │ ├── storage.py # Storage Protocol
126
+ │ ├── memory_storage.py
127
+ │ └── encrypted_storage.py # AES-256-GCM encryption
128
+ └── utils/ # Utilities
129
+ ├── logger.py # Logger interface
130
+ ├── security.py # PII detection, HMAC signing
131
+ ├── platform.py # Platform detection
132
+ └── validators.py # Input validation
133
+ ```
134
+
135
+ ## API Reference
136
+
137
+ ### Initialization
138
+
139
+ ```python
140
+ from flagkit import FlagKit, FlagKitClient, FlagKitOptions
141
+
142
+ # Using the static factory (recommended for single client)
143
+ client = FlagKit.initialize(
144
+ api_key="sdk_...", # Required
145
+ base_url="https://api.flagkit.dev/api/v1", # Optional
146
+ polling_interval=30.0, # Seconds between polls
147
+ enable_polling=True, # Enable background polling
148
+ cache_enabled=True, # Enable local caching
149
+ cache_ttl=300.0, # Cache TTL in seconds
150
+ offline=False, # Offline mode
151
+ timeout=5.0, # Request timeout in seconds
152
+ retries=3, # Number of retries
153
+ bootstrap={"flag": True}, # Initial flag values
154
+ debug=False, # Enable debug logging
155
+ on_ready=lambda: print("Ready!"),
156
+ on_error=lambda e: print(f"Error: {e}"),
157
+ on_update=lambda flags: print(f"Updated: {len(flags)} flags"),
158
+ )
159
+
160
+ # Or create a client directly
161
+ options = FlagKitOptions(api_key="sdk_...")
162
+ client = FlagKitClient(options)
163
+ client.initialize()
164
+ ```
165
+
166
+ ### Flag Evaluation
167
+
168
+ ```python
169
+ # Boolean flags
170
+ enabled = client.get_boolean_value("feature-flag", default=False)
171
+
172
+ # String flags
173
+ variant = client.get_string_value("button-text", default="Click")
174
+
175
+ # Number flags
176
+ limit = client.get_number_value("rate-limit", default=100)
177
+
178
+ # JSON flags
179
+ config = client.get_json_value("config", default={"enabled": False})
180
+
181
+ # Full evaluation result
182
+ result = client.evaluate("feature-flag")
183
+ # result.flag_key, result.value, result.enabled, result.reason, result.version
184
+
185
+ # Evaluate all flags
186
+ all_results = client.evaluate_all()
187
+
188
+ # Check flag existence
189
+ if client.has_flag("my-flag"):
190
+ # ...
191
+
192
+ # Get all flag keys
193
+ keys = client.get_all_flag_keys()
194
+ ```
195
+
196
+ ### Context Management
197
+
198
+ ```python
199
+ from flagkit import EvaluationContext
200
+
201
+ # Set global context
202
+ context = EvaluationContext(
203
+ user_id="user-123",
204
+ email="user@example.com",
205
+ country="US",
206
+ custom={"plan": "premium", "beta_tester": True},
207
+ private_attributes=["email"], # Not sent to server
208
+ )
209
+ client.set_context(context)
210
+
211
+ # Get current context
212
+ current = client.get_context()
213
+
214
+ # Clear context
215
+ client.clear_context()
216
+
217
+ # Identify user (shorthand)
218
+ client.identify("user-123", {"email": "user@example.com"})
219
+
220
+ # Reset to anonymous
221
+ client.reset()
222
+
223
+ # Pass context to evaluation
224
+ result = client.get_boolean_value(
225
+ "feature-flag",
226
+ default=False,
227
+ context=EvaluationContext(user_id="other-user"),
228
+ )
229
+ ```
230
+
231
+ ### Event Tracking
232
+
233
+ ```python
234
+ # Track custom event
235
+ client.track("purchase", {
236
+ "amount": 99.99,
237
+ "currency": "USD",
238
+ "product_id": "prod-123",
239
+ })
240
+
241
+ # Force flush pending events
242
+ client.flush()
243
+ ```
244
+
245
+ ### Lifecycle
246
+
247
+ ```python
248
+ # Check if SDK is ready
249
+ if client.is_ready():
250
+ # ...
251
+
252
+ # Wait for ready (blocks until initialized)
253
+ client.wait_for_ready()
254
+
255
+ # Force refresh flags from server
256
+ client.refresh()
257
+
258
+ # Close SDK and cleanup
259
+ client.close()
260
+
261
+ # Using context manager
262
+ with FlagKitClient(options) as client:
263
+ client.initialize()
264
+ # Use client...
265
+ # Automatically closed
266
+ ```
267
+
268
+ ## Error Handling
269
+
270
+ ```python
271
+ from flagkit import FlagKitError, InitializationError, NetworkError
272
+
273
+ try:
274
+ client = FlagKit.initialize(api_key="sdk_...")
275
+ except InitializationError as e:
276
+ print(f"Failed to initialize: {e.code} - {e}")
277
+ except NetworkError as e:
278
+ if e.recoverable:
279
+ # Retry logic
280
+ pass
281
+ except FlagKitError as e:
282
+ print(f"Error [{e.code}]: {e}")
283
+ print(f"Recoverable: {e.recoverable}")
284
+ print(f"Details: {e.to_dict()}")
285
+ ```
286
+
287
+ ## Local Development
288
+
289
+ ```python
290
+ # Connect to local FlagKit server at http://localhost:8200/api/v1
291
+ client = FlagKit.initialize(
292
+ api_key="sdk_...",
293
+ local_port=8200,
294
+ )
295
+
296
+ # Or use a custom port
297
+ client = FlagKit.initialize(
298
+ api_key="sdk_...",
299
+ local_port=3000, # Uses http://localhost:3000/api/v1
300
+ )
301
+ ```
302
+
303
+ ## Offline Mode
304
+
305
+ ```python
306
+ # Start in offline mode
307
+ client = FlagKit.initialize(
308
+ api_key="sdk_...",
309
+ offline=True,
310
+ bootstrap={"feature-flag": True},
311
+ )
312
+
313
+ # Uses bootstrap values without network requests
314
+ value = client.get_boolean_value("feature-flag", default=False)
315
+ ```
316
+
317
+ ## Security Features
318
+
319
+ ### PII Detection
320
+
321
+ The SDK can detect and warn about potential PII (Personally Identifiable Information) in contexts and events:
322
+
323
+ ```python
324
+ # Enable strict PII mode - raises errors instead of warnings
325
+ client = FlagKit.initialize(
326
+ api_key="sdk_...",
327
+ strict_pii_mode=True,
328
+ )
329
+
330
+ # Attributes containing PII will raise SecurityError
331
+ try:
332
+ client.identify("user-123", {"email": "user@example.com"}) # PII detected!
333
+ except SecurityError as e:
334
+ print(f"PII error: {e}")
335
+
336
+ # Use private attributes to mark fields as intentionally containing PII
337
+ context = EvaluationContext(
338
+ user_id="user-123",
339
+ email="user@example.com",
340
+ private_attributes=["email"], # Marks email as intentionally private
341
+ )
342
+ client.set_context(context) # No error - email marked as private
343
+ ```
344
+
345
+ ### Request Signing
346
+
347
+ POST requests to the FlagKit API are signed with HMAC-SHA256 for integrity:
348
+
349
+ ```python
350
+ # Enabled by default, can be disabled if needed
351
+ client = FlagKit.initialize(
352
+ api_key="sdk_...",
353
+ enable_request_signing=False, # Disable signing
354
+ )
355
+ ```
356
+
357
+ ### Bootstrap Signature Verification
358
+
359
+ Verify bootstrap data integrity using HMAC signatures:
360
+
361
+ ```python
362
+ from flagkit import BootstrapConfig, BootstrapVerificationOptions
363
+ from flagkit.utils.security import create_bootstrap_signature
364
+
365
+ # Create signed bootstrap data
366
+ bootstrap = create_bootstrap_signature(
367
+ flags={"feature-a": True, "feature-b": "value"},
368
+ api_key="sdk_your_api_key",
369
+ )
370
+
371
+ # Use signed bootstrap with verification
372
+ client = FlagKit.initialize(
373
+ api_key="sdk_...",
374
+ bootstrap=bootstrap,
375
+ bootstrap_verification=BootstrapVerificationOptions(
376
+ enabled=True,
377
+ max_age=86400000, # 24 hours in milliseconds
378
+ on_failure="error", # "warn" (default), "error", or "ignore"
379
+ ),
380
+ )
381
+ ```
382
+
383
+ ### Cache Encryption
384
+
385
+ Enable AES-256-GCM encryption for cached flag data:
386
+
387
+ ```python
388
+ client = FlagKit.initialize(
389
+ api_key="sdk_...",
390
+ encrypt_cache=True,
391
+ )
392
+ ```
393
+
394
+ ### Evaluation Jitter (Timing Attack Protection)
395
+
396
+ Add random delays to flag evaluations to prevent cache timing attacks:
397
+
398
+ ```python
399
+ from flagkit import EvaluationJitterConfig
400
+
401
+ client = FlagKit.initialize(
402
+ api_key="sdk_...",
403
+ evaluation_jitter=EvaluationJitterConfig(
404
+ enabled=True,
405
+ min_ms=5,
406
+ max_ms=15,
407
+ ),
408
+ )
409
+ ```
410
+
411
+ ### Error Sanitization
412
+
413
+ Automatically redact sensitive information from error messages:
414
+
415
+ ```python
416
+ from flagkit import ErrorSanitizationConfig
417
+
418
+ client = FlagKit.initialize(
419
+ api_key="sdk_...",
420
+ error_sanitization=ErrorSanitizationConfig(
421
+ enabled=True,
422
+ preserve_original=False, # Set True to keep original for debugging
423
+ ),
424
+ )
425
+ # Errors will have paths, IPs, API keys, and emails redacted
426
+ ```
427
+
428
+ ## Event Persistence
429
+
430
+ Enable crash-resilient event persistence to prevent data loss:
431
+
432
+ ```python
433
+ client = FlagKit.initialize(
434
+ api_key="sdk_...",
435
+ persist_events=True,
436
+ event_storage_path="/path/to/storage", # Optional, defaults to temp dir
437
+ max_persisted_events=10000, # Optional, default 10000
438
+ persistence_flush_interval=1000, # Optional, default 1000ms
439
+ )
440
+ ```
441
+
442
+ Events are written to disk before being sent, and automatically recovered on restart.
443
+
444
+ ## Key Rotation
445
+
446
+ Support seamless API key rotation:
447
+
448
+ ```python
449
+ client = FlagKit.initialize(
450
+ api_key="sdk_primary_key",
451
+ secondary_api_key="sdk_secondary_key",
452
+ key_rotation_grace_period=300.0, # 5 minutes
453
+ )
454
+ # SDK will automatically failover to secondary key on 401 errors
455
+ ```
456
+
457
+ ## Error Handling
458
+
459
+ ```python
460
+ from flagkit import (
461
+ FlagKitError,
462
+ InitializationError,
463
+ NetworkError,
464
+ SecurityError,
465
+ )
466
+
467
+ try:
468
+ client = FlagKit.initialize(api_key="sdk_...")
469
+ except InitializationError as e:
470
+ print(f"Failed to initialize: {e.code} - {e}")
471
+ except SecurityError as e:
472
+ print(f"Security error: {e.code} - {e}")
473
+ except NetworkError as e:
474
+ if e.recoverable:
475
+ # Retry logic
476
+ pass
477
+ except FlagKitError as e:
478
+ print(f"Error [{e.code}]: {e}")
479
+ print(f"Recoverable: {e.recoverable}")
480
+ print(f"Details: {e.to_dict()}")
481
+ ```
482
+
483
+ ### Error Codes
484
+
485
+ | Code | Description |
486
+ |------|-------------|
487
+ | `INIT_FAILED` | SDK initialization failed |
488
+ | `INIT_TIMEOUT` | Initialization timed out |
489
+ | `INIT_ALREADY_INITIALIZED` | SDK already initialized |
490
+ | `INIT_NOT_INITIALIZED` | SDK not initialized |
491
+ | `NETWORK_ERROR` | Network request failed |
492
+ | `AUTH_INVALID_KEY` | Invalid API key |
493
+ | `SECURITY_PII_DETECTED` | PII detected in strict mode |
494
+ | `SECURITY_LOCAL_PORT_IN_PRODUCTION` | Local port used in production |
495
+ | `SECURITY_SIGNATURE_INVALID` | Bootstrap signature verification failed |
496
+
497
+ ## Configuration Options
498
+
499
+ | Option | Type | Default | Description |
500
+ |--------|------|---------|-------------|
501
+ | `api_key` | str | Required | API key for authentication |
502
+ | `secondary_api_key` | str | None | Secondary key for rotation |
503
+ | `key_rotation_grace_period` | float | 300.0 | Grace period in seconds |
504
+ | `base_url` | str | `https://api.flagkit.dev/api/v1` | API base URL |
505
+ | `local_port` | int | None | Local development port |
506
+ | `polling_interval` | float | 30.0 | Polling interval in seconds |
507
+ | `enable_polling` | bool | True | Enable background polling |
508
+ | `cache_enabled` | bool | True | Enable local caching |
509
+ | `cache_ttl` | float | 300.0 | Cache TTL in seconds |
510
+ | `encrypt_cache` | bool | False | Enable AES-256-GCM cache encryption |
511
+ | `offline` | bool | False | Offline mode |
512
+ | `timeout` | float | 5.0 | Request timeout in seconds |
513
+ | `retries` | int | 3 | Number of retry attempts |
514
+ | `bootstrap` | dict/BootstrapConfig | {} | Initial flag values |
515
+ | `bootstrap_verification` | BootstrapVerificationOptions | enabled | Bootstrap verification settings |
516
+ | `debug` | bool | False | Enable debug logging |
517
+ | `logger` | LoggerProtocol | None | Custom logger |
518
+ | `on_ready` | Callable | None | Ready callback |
519
+ | `on_error` | Callable | None | Error callback |
520
+ | `on_update` | Callable | None | Update callback |
521
+ | `enable_request_signing` | bool | True | Enable request signing |
522
+ | `strict_pii_mode` | bool | False | Error on PII detection |
523
+ | `persist_events` | bool | False | Enable event persistence |
524
+ | `event_storage_path` | str | temp dir | Event storage directory |
525
+ | `max_persisted_events` | int | 10000 | Max persisted events |
526
+ | `persistence_flush_interval` | int | 1000 | Persistence flush interval (ms) |
527
+ | `evaluation_jitter` | EvaluationJitterConfig | disabled | Timing attack protection |
528
+ | `error_sanitization` | ErrorSanitizationConfig | enabled | Redact sensitive info from errors |
529
+
530
+ ## Testing
531
+
532
+ ```python
533
+ # Use offline mode with bootstrap values
534
+ client = FlagKit.initialize(
535
+ api_key="sdk_test",
536
+ offline=True,
537
+ bootstrap={"feature-flag": True},
538
+ )
539
+ client.wait_for_ready()
540
+
541
+ # With signed bootstrap for verification testing
542
+ from flagkit.utils.security import create_bootstrap_signature
543
+
544
+ bootstrap = create_bootstrap_signature(
545
+ flags={"feature-flag": True},
546
+ api_key="sdk_test",
547
+ )
548
+ client = FlagKit.initialize(
549
+ api_key="sdk_test",
550
+ offline=True,
551
+ bootstrap=bootstrap,
552
+ )
553
+ ```
554
+
555
+ ## Thread Safety
556
+
557
+ All SDK methods are safe for concurrent use from multiple threads. The client uses internal synchronization (threading.Lock) to ensure thread-safe access to:
558
+
559
+ - Flag cache
560
+ - Event queue
561
+ - Context management
562
+ - Polling state
563
+
564
+ ## Development
565
+
566
+ ```bash
567
+ # Install dev dependencies
568
+ pip install -e ".[dev]"
569
+
570
+ # Run tests
571
+ pytest
572
+
573
+ # Run with coverage
574
+ pytest --cov
575
+
576
+ # Type checking
577
+ mypy src/
578
+
579
+ # Linting
580
+ ruff check src/
581
+ ```
582
+
583
+ ## License
584
+
585
+ MIT License - see [LICENSE](LICENSE) for details.