toggletest 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.
- toggletest-0.1.0/PKG-INFO +12 -0
- toggletest-0.1.0/README.md +289 -0
- toggletest-0.1.0/pyproject.toml +18 -0
- toggletest-0.1.0/toggletest/__init__.py +54 -0
- toggletest-0.1.0/toggletest/client.py +520 -0
- toggletest-0.1.0/toggletest/event_buffer.py +244 -0
- toggletest-0.1.0/toggletest/rules_store.py +257 -0
- toggletest-0.1.0/toggletest/sse.py +217 -0
- toggletest-0.1.0/toggletest/types.py +156 -0
- toggletest-0.1.0/toggletest/wasm_engine.py +673 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: toggletest
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: ToggleTest SDK for feature flags and A/B testing
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Requires-Dist: httpx-sse>=0.4
|
|
8
|
+
Requires-Dist: httpx>=0.25
|
|
9
|
+
Requires-Dist: wasmtime>=27.0
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
12
|
+
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# ToggleTest Python SDK
|
|
2
|
+
|
|
3
|
+
Python SDK for feature flags and A/B testing with local WASM-based evaluation. After initialization, all flag evaluations happen locally with zero network latency.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install toggletest
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from toggletest import ToggleTestClient
|
|
15
|
+
|
|
16
|
+
# Initialize the client
|
|
17
|
+
client = ToggleTestClient(
|
|
18
|
+
api_key="tt_your_api_key",
|
|
19
|
+
base_url="https://api.toggletest.com",
|
|
20
|
+
environment="production" # optional
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Start the SDK (fetches WASM evaluator and rules)
|
|
24
|
+
client.start()
|
|
25
|
+
|
|
26
|
+
# Evaluate a feature flag
|
|
27
|
+
if client.is_enabled("dark-mode", user_id="user-123"):
|
|
28
|
+
print("Dark mode is enabled")
|
|
29
|
+
|
|
30
|
+
# Get A/B test variant
|
|
31
|
+
result = client.get_variant("pricing-test", user_id="user-123")
|
|
32
|
+
print(f"User assigned to variant: {result.variant}")
|
|
33
|
+
|
|
34
|
+
# Track an event
|
|
35
|
+
client.track(
|
|
36
|
+
"conversion",
|
|
37
|
+
end_user_id="user-123",
|
|
38
|
+
test_key="pricing-test",
|
|
39
|
+
variant_name=result.variant
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Shutdown gracefully
|
|
43
|
+
client.close()
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## API Reference
|
|
47
|
+
|
|
48
|
+
### Client Initialization
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
client = ToggleTestClient(
|
|
52
|
+
api_key="tt_your_api_key",
|
|
53
|
+
base_url="https://api.toggletest.com",
|
|
54
|
+
environment="production", # optional
|
|
55
|
+
on_ready=lambda: print("SDK ready"), # optional callback
|
|
56
|
+
on_error=lambda e: print(f"Error: {e}") # optional error handler
|
|
57
|
+
)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Lifecycle Methods
|
|
61
|
+
|
|
62
|
+
#### `start()`
|
|
63
|
+
Initialize the SDK. Fetches the WASM evaluator binary and rules, then starts the SSE connection for real-time updates.
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
client.start()
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Raises `RuntimeError` if WASM loading fails or `httpx.HTTPError` if rules fetch fails.
|
|
70
|
+
|
|
71
|
+
#### `close()`
|
|
72
|
+
Gracefully shut down the SDK. Disconnects SSE, flushes buffered events, and releases resources.
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
client.close()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### `ready` (property)
|
|
79
|
+
Check if the SDK has completed initialization.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
if client.ready:
|
|
83
|
+
# SDK is ready for evaluations
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Feature Flag Evaluation
|
|
87
|
+
|
|
88
|
+
#### `is_enabled(flag_key, *, user_id, attributes=None)`
|
|
89
|
+
Returns a boolean indicating if a flag is enabled. Returns `False` if the flag is not found.
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
enabled = client.is_enabled(
|
|
93
|
+
"dark-mode",
|
|
94
|
+
user_id="user-123",
|
|
95
|
+
attributes={"plan": "pro", "country": "US"}
|
|
96
|
+
)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### `evaluate_flag(flag_key, *, user_id, attributes=None)`
|
|
100
|
+
Returns the full flag evaluation result including value, reason, and rule ID.
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from toggletest import FlagResult
|
|
104
|
+
|
|
105
|
+
result = client.evaluate_flag(
|
|
106
|
+
"feature-x",
|
|
107
|
+
user_id="user-123",
|
|
108
|
+
attributes={"beta": True}
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
print(f"Value: {result.value}")
|
|
112
|
+
print(f"Reason: {result.reason}")
|
|
113
|
+
print(f"Rule ID: {result.rule_id}")
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Returns:** `FlagResult` with:
|
|
117
|
+
- `value`: The flag value (bool, str, number, or dict)
|
|
118
|
+
- `reason`: Evaluation reason (e.g., "rule_match", "default", "disabled", "not_found")
|
|
119
|
+
- `rule_id`: ID of the matched rule, if any
|
|
120
|
+
|
|
121
|
+
#### `evaluate_all(*, user_id, attributes=None)`
|
|
122
|
+
Evaluate all flags and tests for the given user. Useful for bootstrapping client-side SDKs.
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from toggletest import EvalResults
|
|
126
|
+
|
|
127
|
+
results = client.evaluate_all(
|
|
128
|
+
user_id="user-123",
|
|
129
|
+
attributes={"plan": "enterprise"}
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Access all flags
|
|
133
|
+
for flag_key, flag_result in results.flags.items():
|
|
134
|
+
print(f"{flag_key}: {flag_result.value}")
|
|
135
|
+
|
|
136
|
+
# Access all tests
|
|
137
|
+
for test_key, test_result in results.tests.items():
|
|
138
|
+
print(f"{test_key}: {test_result.variant}")
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Returns:** `EvalResults` with:
|
|
142
|
+
- `flags`: Dict[str, FlagResult] - All flag evaluations
|
|
143
|
+
- `tests`: Dict[str, TestResult] - All test assignments
|
|
144
|
+
|
|
145
|
+
### A/B Testing
|
|
146
|
+
|
|
147
|
+
#### `get_variant(test_key, *, user_id, attributes=None)`
|
|
148
|
+
Get the variant assignment for an A/B test. Users are deterministically assigned based on a hash of user_id + test_key.
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from toggletest import TestResult
|
|
152
|
+
|
|
153
|
+
result = client.get_variant(
|
|
154
|
+
"pricing-test",
|
|
155
|
+
user_id="user-123",
|
|
156
|
+
attributes={"plan": "pro"}
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
print(f"Variant: {result.variant}") # e.g., "control", "variant_a"
|
|
160
|
+
print(f"Bucket: {result.bucket}") # 0-9999
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Returns:** `TestResult` with:
|
|
164
|
+
- `variant`: Variant name (e.g., "control", "variant_a")
|
|
165
|
+
- `bucket`: Deterministic bucket value (0-9999)
|
|
166
|
+
|
|
167
|
+
### Event Tracking
|
|
168
|
+
|
|
169
|
+
#### `track(event_type, *, end_user_id, test_key=None, flag_key=None, variant_name=None, evaluation_reason=None, metadata=None)`
|
|
170
|
+
Track an analytics event. Events are buffered and sent in batches.
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
# Track a conversion
|
|
174
|
+
client.track(
|
|
175
|
+
"conversion",
|
|
176
|
+
end_user_id="user-123",
|
|
177
|
+
test_key="pricing-test",
|
|
178
|
+
variant_name="variant_a",
|
|
179
|
+
metadata={"revenue": 99.99}
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Track a flag evaluation
|
|
183
|
+
client.track(
|
|
184
|
+
"flag_evaluation",
|
|
185
|
+
end_user_id="user-123",
|
|
186
|
+
flag_key="dark-mode",
|
|
187
|
+
evaluation_reason="rule_match"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# Track a variant assignment
|
|
191
|
+
client.track(
|
|
192
|
+
"variant_assignment",
|
|
193
|
+
end_user_id="user-123",
|
|
194
|
+
test_key="homepage-redesign",
|
|
195
|
+
variant_name="control"
|
|
196
|
+
)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Common event types:
|
|
200
|
+
- `"conversion"` - User completed a goal
|
|
201
|
+
- `"flag_evaluation"` - A flag was evaluated
|
|
202
|
+
- `"variant_assignment"` - User was assigned to a variant
|
|
203
|
+
|
|
204
|
+
### Listeners
|
|
205
|
+
|
|
206
|
+
#### `on_rules_update(listener)`
|
|
207
|
+
Register a callback for rules updates. The listener receives the new rules version hash.
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
def on_update(version_hash):
|
|
211
|
+
print(f"Rules updated to version: {version_hash}")
|
|
212
|
+
|
|
213
|
+
unsubscribe = client.on_rules_update(on_update)
|
|
214
|
+
|
|
215
|
+
# Later, to unsubscribe:
|
|
216
|
+
unsubscribe()
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Configuration Options
|
|
220
|
+
|
|
221
|
+
| Parameter | Type | Required | Description |
|
|
222
|
+
|-----------|------|----------|-------------|
|
|
223
|
+
| `api_key` | str | Yes | SDK API key (typically prefixed with "tt_") |
|
|
224
|
+
| `base_url` | str | Yes | Base URL of the ToggleTest API |
|
|
225
|
+
| `environment` | str | No | Environment identifier (e.g., "production", "staging") |
|
|
226
|
+
| `on_ready` | Callable | No | Callback invoked when SDK initialization completes |
|
|
227
|
+
| `on_error` | Callable | No | Callback invoked on non-fatal errors (SSE disconnect, etc.) |
|
|
228
|
+
|
|
229
|
+
## User Context and Attributes
|
|
230
|
+
|
|
231
|
+
The `attributes` parameter accepts a dictionary of user/request attributes for targeting rules:
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
client.is_enabled(
|
|
235
|
+
"premium-feature",
|
|
236
|
+
user_id="user-123",
|
|
237
|
+
attributes={
|
|
238
|
+
"plan": "enterprise",
|
|
239
|
+
"country": "US",
|
|
240
|
+
"beta_tester": True,
|
|
241
|
+
"account_age_days": 45
|
|
242
|
+
}
|
|
243
|
+
)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Architecture
|
|
247
|
+
|
|
248
|
+
The SDK uses WASM-based local evaluation for zero-latency flag checks:
|
|
249
|
+
|
|
250
|
+
1. **Initialization** - Downloads WASM evaluator binary and rules JSON
|
|
251
|
+
2. **Local Evaluation** - All flag/test evaluations run in-process via WASM
|
|
252
|
+
3. **Real-time Updates** - SSE connection for instant rules change notifications
|
|
253
|
+
4. **Event Batching** - Analytics events are buffered and sent periodically
|
|
254
|
+
|
|
255
|
+
After `start()` completes, all evaluation methods are synchronous with no network calls in the hot path.
|
|
256
|
+
|
|
257
|
+
## Error Handling
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
from toggletest import ToggleTestClient
|
|
261
|
+
|
|
262
|
+
client = ToggleTestClient(
|
|
263
|
+
api_key="tt_key",
|
|
264
|
+
base_url="https://api.toggletest.com",
|
|
265
|
+
on_error=lambda e: print(f"SDK Error: {e}")
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
client.start()
|
|
270
|
+
except RuntimeError as e:
|
|
271
|
+
print(f"Failed to start SDK: {e}")
|
|
272
|
+
|
|
273
|
+
# Evaluation methods raise RuntimeError if SDK is not ready
|
|
274
|
+
if not client.ready:
|
|
275
|
+
print("SDK not ready")
|
|
276
|
+
else:
|
|
277
|
+
result = client.evaluate_flag("feature", user_id="user-123")
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Requirements
|
|
281
|
+
|
|
282
|
+
- Python 3.9+
|
|
283
|
+
- httpx >= 0.25
|
|
284
|
+
- httpx-sse >= 0.4
|
|
285
|
+
- wasmtime >= 27.0
|
|
286
|
+
|
|
287
|
+
## License
|
|
288
|
+
|
|
289
|
+
MIT
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "toggletest"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "ToggleTest SDK for feature flags and A/B testing"
|
|
9
|
+
requires-python = ">=3.9"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"httpx>=0.25",
|
|
13
|
+
"httpx-sse>=0.4",
|
|
14
|
+
"wasmtime>=27.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
dev = ["pytest", "pytest-asyncio"]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
toggletest - ToggleTest Python SDK with WASM-based local evaluation
|
|
3
|
+
|
|
4
|
+
This is the package entry point. It re-exports only the types and classes
|
|
5
|
+
that SDK consumers need. Internal implementation details (WasmEngine,
|
|
6
|
+
RulesStore, SseConnection, EventBuffer) are NOT exported.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
from toggletest import ToggleTestClient
|
|
10
|
+
from toggletest import EvalContext, EvalResults, FlagResult, TestResult
|
|
11
|
+
|
|
12
|
+
client = ToggleTestClient(
|
|
13
|
+
api_key="tt_...",
|
|
14
|
+
base_url="http://localhost:3001",
|
|
15
|
+
environment="production",
|
|
16
|
+
)
|
|
17
|
+
client.start()
|
|
18
|
+
|
|
19
|
+
if client.is_enabled("dark-mode", user_id="user-123"):
|
|
20
|
+
...
|
|
21
|
+
|
|
22
|
+
client.close()
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
# -- Main client class --
|
|
26
|
+
from .client import ToggleTestClient
|
|
27
|
+
|
|
28
|
+
# -- Configuration and context types --
|
|
29
|
+
from .types import ToggleTestConfig, EvalContext
|
|
30
|
+
|
|
31
|
+
# -- Evaluation result types --
|
|
32
|
+
from .types import FlagResult, TestResult, EvalResults
|
|
33
|
+
|
|
34
|
+
# -- Event tracking types --
|
|
35
|
+
from .types import SdkEvent
|
|
36
|
+
|
|
37
|
+
# -- Listener types --
|
|
38
|
+
from .types import RulesUpdateListener
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
# Client
|
|
42
|
+
"ToggleTestClient",
|
|
43
|
+
# Config / context
|
|
44
|
+
"ToggleTestConfig",
|
|
45
|
+
"EvalContext",
|
|
46
|
+
# Evaluation results
|
|
47
|
+
"FlagResult",
|
|
48
|
+
"TestResult",
|
|
49
|
+
"EvalResults",
|
|
50
|
+
# Events
|
|
51
|
+
"SdkEvent",
|
|
52
|
+
# Listeners
|
|
53
|
+
"RulesUpdateListener",
|
|
54
|
+
]
|