featurevisor 2.0.0__tar.gz → 3.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 (46) hide show
  1. {featurevisor-2.0.0/src/featurevisor.egg-info → featurevisor-3.0.0}/PKG-INFO +77 -26
  2. {featurevisor-2.0.0 → featurevisor-3.0.0}/README.md +76 -25
  3. featurevisor-3.0.0/conformance/sdk-v3.json +876 -0
  4. {featurevisor-2.0.0 → featurevisor-3.0.0}/pyproject.toml +1 -1
  5. featurevisor-3.0.0/src/featurevisor/child.py +151 -0
  6. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor/cli.py +2 -2
  7. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor/evaluate.py +63 -28
  8. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor/evaluation_data_provider.py +13 -1
  9. featurevisor-3.0.0/src/featurevisor/events.py +150 -0
  10. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor/instance.py +163 -38
  11. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor/modules.py +26 -0
  12. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor/openfeature.py +21 -5
  13. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor/tester.py +68 -12
  14. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor/types.py +30 -1
  15. {featurevisor-2.0.0 → featurevisor-3.0.0/src/featurevisor.egg-info}/PKG-INFO +77 -26
  16. featurevisor-3.0.0/tests/test_conformance.py +210 -0
  17. {featurevisor-2.0.0 → featurevisor-3.0.0}/tests/test_instance_parity.py +4 -4
  18. {featurevisor-2.0.0 → featurevisor-3.0.0}/tests/test_sdk.py +11 -11
  19. {featurevisor-2.0.0 → featurevisor-3.0.0}/tests_openfeature/test_provider.py +35 -0
  20. featurevisor-2.0.0/conformance/sdk-v3.json +0 -218
  21. featurevisor-2.0.0/src/featurevisor/child.py +0 -119
  22. featurevisor-2.0.0/src/featurevisor/events.py +0 -40
  23. featurevisor-2.0.0/tests/test_conformance.py +0 -87
  24. {featurevisor-2.0.0 → featurevisor-3.0.0}/LICENSE +0 -0
  25. {featurevisor-2.0.0 → featurevisor-3.0.0}/MANIFEST.in +0 -0
  26. {featurevisor-2.0.0 → featurevisor-3.0.0}/setup.cfg +0 -0
  27. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor/__init__.py +0 -0
  28. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor/__main__.py +0 -0
  29. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor/bucketer.py +0 -0
  30. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor/compare_versions.py +0 -0
  31. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor/conditions.py +0 -0
  32. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor/diagnostics.py +0 -0
  33. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor/emitter.py +0 -0
  34. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor/helpers.py +0 -0
  35. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor/murmurhash.py +0 -0
  36. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor/project.py +0 -0
  37. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor/py.typed +0 -0
  38. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor.egg-info/SOURCES.txt +0 -0
  39. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor.egg-info/dependency_links.txt +0 -0
  40. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor.egg-info/entry_points.txt +0 -0
  41. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor.egg-info/requires.txt +0 -0
  42. {featurevisor-2.0.0 → featurevisor-3.0.0}/src/featurevisor.egg-info/top_level.txt +0 -0
  43. {featurevisor-2.0.0 → featurevisor-3.0.0}/tests/test_cli.py +0 -0
  44. {featurevisor-2.0.0 → featurevisor-3.0.0}/tests/test_conditions_parity.py +0 -0
  45. {featurevisor-2.0.0 → featurevisor-3.0.0}/tests/test_evaluation_data_provider.py +0 -0
  46. {featurevisor-2.0.0 → featurevisor-3.0.0}/tests/test_helpers_parity.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: featurevisor
3
- Version: 2.0.0
3
+ Version: 3.0.0
4
4
  Summary: Featurevisor Python SDK
5
5
  Author: Fahad Heylaal
6
6
  License-Expression: MIT
@@ -50,10 +50,11 @@ This SDK is compatible with Featurevisor v3 projects and v2 datafiles.
50
50
  - [Manually passing context](#manually-passing-context)
51
51
  - [Check if enabled](#check-if-enabled)
52
52
  - [Getting variation](#getting-variation)
53
- - [Getting variables](#getting-variables)
54
- - [Type specific methods](#type-specific-methods)
55
- - [Getting all evaluations](#getting-all-evaluations)
56
- - [Sticky](#sticky)
53
+ - [Getting feature variables](#getting-feature-variables)
54
+ - [Getting global variables](#getting-global-variables)
55
+ - [Type specific methods](#type-specific-methods)
56
+ - [Getting evaluation snapshots](#getting-evaluation-snapshots)
57
+ - [Sticky features and variables](#sticky-features-and-variables)
57
58
  - [Initialize with sticky](#initialize-with-sticky)
58
59
  - [Set sticky afterwards](#set-sticky-afterwards)
59
60
  - [Setting datafile](#setting-datafile)
@@ -68,7 +69,7 @@ This SDK is compatible with Featurevisor v3 projects and v2 datafiles.
68
69
  - [Events](#events)
69
70
  - [`datafile_set`](#datafile_set)
70
71
  - [`context_set`](#context_set)
71
- - [`sticky_set`](#sticky_set)
72
+ - [`sticky_features_set` and `sticky_variables_set`](#sticky_features_set-and-sticky_variables_set)
72
73
  - [`error`](#error)
73
74
  - [Modules](#modules)
74
75
  - [Defining a module](#defining-a-module)
@@ -113,7 +114,7 @@ f: Featurevisor = create_featurevisor({
113
114
 
114
115
  Most applications only need `create_featurevisor` and the `Featurevisor` instance type. Public extension and observability APIs include `FeaturevisorModule`, diagnostics, events, and the datafile dictionaries accepted by the factory.
115
116
 
116
- Concurrent evaluations are safe after an instance is configured. Do not mutate or close the same instance concurrently with evaluations. Serialize calls to `set_datafile`, `set_context`, `set_sticky`, `add_module`, `remove_module`, and `close`. Module, event, and diagnostic callbacks must synchronize mutable state that they capture.
117
+ Concurrent evaluations are safe after an instance is configured. Do not mutate or close the same instance concurrently with evaluations. Serialize calls to `set_datafile`, `set_context`, `set_sticky_features`, `set_sticky_variables`, `add_module`, `remove_module`, and `close`. Module, event, and diagnostic callbacks must synchronize mutable state that they capture.
117
118
 
118
119
  ## Initialization
119
120
 
@@ -137,11 +138,12 @@ f = create_featurevisor({
137
138
 
138
139
  ## Evaluation types
139
140
 
140
- We can evaluate 3 types of values against a particular [feature](https://featurevisor.com/docs/features/):
141
+ Featurevisor evaluates flags, variations, feature variables, and global variables:
141
142
 
142
143
  - [**Flag**](#check-if-enabled) (`bool`): whether the feature is enabled or not
143
144
  - [**Variation**](#getting-variation) (`string`): the variation of the feature (if any)
144
- - [**Variables**](#getting-variables): variable values of the feature (if any)
145
+ - [**Feature variables**](#getting-feature-variables): variable values owned by a feature
146
+ - [**Global variables**](#getting-global-variables): independently evaluated project values
145
147
 
146
148
  ## Context
147
149
 
@@ -200,7 +202,8 @@ You can also pass additional per-evaluation context:
200
202
  ```python
201
203
  is_enabled = f.is_enabled("my_feature", {"country": "nl"})
202
204
  variation = f.get_variation("my_feature", {"country": "nl"})
203
- variable_value = f.get_variable("my_feature", "my_variable", {"country": "nl"})
205
+ feature_variable = f.get_variable("my_feature", "my_variable", {"country": "nl"})
206
+ global_variable = f.get_variable("supportEmail", {"country": "nl"})
204
207
  ```
205
208
 
206
209
  ## Check if enabled
@@ -219,13 +222,24 @@ if variation == "treatment":
219
222
  pass
220
223
  ```
221
224
 
222
- ## Getting variables
225
+ ## Getting feature variables
223
226
 
224
227
  ```python
225
228
  bg_color = f.get_variable("my_feature", "bgColor")
226
229
  ```
227
230
 
228
- ### Type specific methods
231
+ ## Getting global variables
232
+
233
+ Global variables use the same methods without a feature key:
234
+
235
+ ```python
236
+ support_email = f.get_variable("supportEmail", {"country": "nl"})
237
+ evaluation = f.evaluate_variable("supportEmail", {"country": "nl"})
238
+ ```
239
+
240
+ Global variables honour sticky values, required features, and the first matching override. When an override contains both conditions and segments, both must match.
241
+
242
+ ## Type specific methods
229
243
 
230
244
  Typed convenience methods are also available:
231
245
 
@@ -239,33 +253,44 @@ f.get_variable_object(feature_key, variable_key, context={})
239
253
  f.get_variable_json(feature_key, variable_key, context={})
240
254
  ```
241
255
 
256
+ The same helpers evaluate global variables when the feature key is omitted:
257
+
258
+ ```python
259
+ f.get_variable_string("supportEmail", context={})
260
+ f.get_variable_object("checkoutSettings", context={})
261
+ ```
262
+
242
263
  Type specific methods do not coerce values. `get_variable_integer()` returns `None` for the string `"1"`, and boolean getters return `None` for non-boolean values.
243
264
 
244
- ## Getting all evaluations
265
+ ## Getting evaluation snapshots
245
266
 
246
267
  ```python
247
- all_evaluations = f.get_all_evaluations()
268
+ features = f.get_feature_evaluations()
269
+ variables = f.get_variable_evaluations()
248
270
  ```
249
271
 
250
- ## Sticky
272
+ ## Sticky features and variables
251
273
 
252
274
  ### Initialize with sticky
253
275
 
254
276
  You can pin feature evaluations with sticky values:
255
277
 
256
- Sticky values belong to an SDK or child instance. Evaluation options do not accept sticky overrides; use `spawn(context, {"sticky": ...})` when a child needs its own sticky state.
278
+ Sticky values belong to an SDK or child instance. Evaluation options do not accept sticky overrides. Pass `stickyFeatures` or `stickyVariables` when a child needs its own state.
257
279
 
258
280
  ```python
259
281
  f = create_featurevisor({
260
- "sticky": {
282
+ "stickyFeatures": {
261
283
  "myFeatureKey": {
262
284
  "enabled": True,
263
285
  "variation": "treatment",
264
286
  "variables": {
265
287
  "myVariableKey": "myVariableValue",
266
288
  },
267
- }
268
- }
289
+ },
290
+ },
291
+ "stickyVariables": {
292
+ "supportEmail": "sticky@example.com",
293
+ },
269
294
  })
270
295
  ```
271
296
 
@@ -274,11 +299,13 @@ f = create_featurevisor({
274
299
  Or update them later:
275
300
 
276
301
  ```python
277
- f.set_sticky({
302
+ f.set_sticky_features({
278
303
  "myFeatureKey": {
279
304
  "enabled": False,
280
305
  }
281
306
  })
307
+
308
+ f.set_sticky_variables({"supportEmail": "new@example.com"})
282
309
  ```
283
310
 
284
311
  ## Setting datafile
@@ -420,10 +447,13 @@ unsubscribe = f.on("context_set", lambda event: print(event["context"]))
420
447
  unsubscribe()
421
448
  ```
422
449
 
423
- ### `sticky_set`
450
+ ### `sticky_features_set` and `sticky_variables_set`
424
451
 
425
452
  ```python
426
- unsubscribe = f.on("sticky_set", lambda event: print(event["features"]))
453
+ unsubscribe = f.on("sticky_features_set", lambda event: print(event["features"]))
454
+ unsubscribe()
455
+
456
+ unsubscribe = f.on("sticky_variables_set", lambda event: print(event["variables"]))
427
457
  unsubscribe()
428
458
  ```
429
459
 
@@ -468,6 +498,8 @@ And optionally these properties depending on whether you are evaluating a featur
468
498
  - `variableValue`: the variable value
469
499
  - `variableSchema`: the variable schema
470
500
  - `variableOverrideIndex`: index of matched variable override when applicable
501
+ - `variableOverrideKey`: key of the matched variable override when available
502
+ - `variableOverridePath`: nested authoring path of the matched global variable override
471
503
 
472
504
  ## Modules
473
505
 
@@ -478,6 +510,8 @@ Modules can intercept evaluation and participate in SDK lifecycle:
478
510
  - `bucketKey`
479
511
  - `bucketValue`
480
512
  - `after`
513
+ - `beforeEvaluation`
514
+ - `afterEvaluation`
481
515
  - `close`
482
516
 
483
517
  ### Defining a module
@@ -487,13 +521,17 @@ my_module = {
487
521
  "name": "my-module",
488
522
  "setup": lambda api: api["onDiagnostic"](lambda diagnostic: print(diagnostic)),
489
523
  "before": lambda options: {**options, "context": {**options["context"], "country": "nl"}},
524
+ "beforeEvaluation": lambda options: options,
490
525
  "bucketKey": lambda options: options["bucketKey"],
491
526
  "bucketValue": lambda options: options["bucketValue"],
492
527
  "after": lambda evaluation, options: evaluation,
528
+ "afterEvaluation": lambda evaluation, options: evaluation,
493
529
  "close": lambda: None,
494
530
  }
495
531
  ```
496
532
 
533
+ For feature evaluations, all `before` callbacks run in registration order, followed by all `beforeEvaluation` callbacks. After evaluation and caller defaults, all `afterEvaluation` callbacks run, followed by all `after` callbacks. Global variable evaluations use only `beforeEvaluation` and `afterEvaluation`. Required feature checks run through the complete module pipeline, and transformed defaults are preserved.
534
+
497
535
  The module API passed to `setup` exposes `getRevision`, `onDiagnostic`, and `reportDiagnostic`.
498
536
 
499
537
  If `setup` raises an exception, the module is not registered. Featurevisor removes subscriptions created during setup, reports `module_setup_error`, and calls `close` when present.
@@ -524,6 +562,7 @@ child.is_enabled("my_feature")
524
562
  child.evaluate_flag("my_feature")
525
563
  child.evaluate_variation("my_feature")
526
564
  child.evaluate_variable("my_feature", "my_variable")
565
+ child.evaluate_variable("supportEmail")
527
566
  ```
528
567
 
529
568
  ## Close
@@ -602,6 +641,16 @@ python -m featurevisor benchmark \
602
641
  --context='{"userId":"123"}'
603
642
  ```
604
643
 
644
+ For a global variable, omit `--feature`:
645
+
646
+ ```bash
647
+ python -m featurevisor benchmark \
648
+ --projectDirectoryPath=/path/to/featurevisor-project \
649
+ --environment=production \
650
+ --variable=supportEmail \
651
+ --context='{"country":"nl"}'
652
+ ```
653
+
605
654
  ### Assess distribution
606
655
 
607
656
  Inspect enabled/disabled and variation distribution over repeated evaluations:
@@ -665,6 +714,7 @@ Call `api.shutdown()` during application shutdown. This closes a Featurevisor in
665
714
  | `checkout` | Boolean flag for `checkout` |
666
715
  | `checkout:variation` | Variation value for `checkout` |
667
716
  | `checkout:title` | Variable `title` for `checkout` |
717
+ | `variable:supportEmail` | Global variable `supportEmail` |
668
718
 
669
719
  Boolean variables use the boolean resolver. Integer and double variables use their matching numeric resolvers. Arrays, objects, and JSON variables use the object resolver.
670
720
 
@@ -675,10 +725,11 @@ provider = FeaturevisorOpenFeatureProvider(
675
725
  {"datafile": datafile_content},
676
726
  key_separator="/",
677
727
  variation_key="$variation",
728
+ global_variable_prefix="$variable",
678
729
  )
679
730
  ```
680
731
 
681
- This makes `checkout/$variation` the variation key and `checkout/title` a variable key.
732
+ This makes `checkout/$variation` the variation key, `checkout/title` a feature variable key, and `$variable/supportEmail` a global variable key. The global prefix defaults to `variable` and cannot contain the separator.
682
733
 
683
734
  ### Context mapping
684
735
 
@@ -699,9 +750,9 @@ The provider maps Featurevisor evaluation results to OpenFeature details:
699
750
 
700
751
  | Featurevisor result | OpenFeature result |
701
752
  | --- | --- |
702
- | Required, forced, sticky, or rule match | `TARGETING_MATCH` |
753
+ | Required feature rule, forced, sticky, or rule match | `TARGETING_MATCH` |
703
754
  | Traffic allocation | `SPLIT` |
704
- | Disabled variation or variable | `DISABLED` |
755
+ | Unmet global variable requirements, disabled variation, or disabled variable | `DISABLED` |
705
756
  | No match or variable default | `DEFAULT` |
706
757
  | Missing feature, variable, or variations | `ERROR` with `FLAG_NOT_FOUND` |
707
758
  | Wrong resolver type | `ERROR` with `TYPE_MISMATCH` |
@@ -710,7 +761,7 @@ The provider maps Featurevisor evaluation results to OpenFeature details:
710
761
 
711
762
  Errors return the default value supplied to OpenFeature. A malformed datafile uses the stable message `Could not parse datafile`. A later successful `set_datafile` call clears the parse error.
712
763
 
713
- Resolution metadata can include `featureKey`, `variableKey`, `featurevisorReason`, `revision`, `schemaVersion`, `ruleKey`, `bucketKey`, `bucketValue`, `forceIndex`, and `variableOverrideIndex`. The selected variation is exposed as the OpenFeature variant when available.
764
+ Resolution metadata can include `featureKey`, `variableKey`, `featurevisorReason`, `revision`, `schemaVersion`, `ruleKey`, `bucketKey`, `bucketValue`, `forceIndex`, `variableOverrideIndex`, and `variableOverrideKey`. The selected variation is exposed as the OpenFeature variant when available.
714
765
 
715
766
  ### Tracking
716
767
 
@@ -19,10 +19,11 @@ This SDK is compatible with Featurevisor v3 projects and v2 datafiles.
19
19
  - [Manually passing context](#manually-passing-context)
20
20
  - [Check if enabled](#check-if-enabled)
21
21
  - [Getting variation](#getting-variation)
22
- - [Getting variables](#getting-variables)
23
- - [Type specific methods](#type-specific-methods)
24
- - [Getting all evaluations](#getting-all-evaluations)
25
- - [Sticky](#sticky)
22
+ - [Getting feature variables](#getting-feature-variables)
23
+ - [Getting global variables](#getting-global-variables)
24
+ - [Type specific methods](#type-specific-methods)
25
+ - [Getting evaluation snapshots](#getting-evaluation-snapshots)
26
+ - [Sticky features and variables](#sticky-features-and-variables)
26
27
  - [Initialize with sticky](#initialize-with-sticky)
27
28
  - [Set sticky afterwards](#set-sticky-afterwards)
28
29
  - [Setting datafile](#setting-datafile)
@@ -37,7 +38,7 @@ This SDK is compatible with Featurevisor v3 projects and v2 datafiles.
37
38
  - [Events](#events)
38
39
  - [`datafile_set`](#datafile_set)
39
40
  - [`context_set`](#context_set)
40
- - [`sticky_set`](#sticky_set)
41
+ - [`sticky_features_set` and `sticky_variables_set`](#sticky_features_set-and-sticky_variables_set)
41
42
  - [`error`](#error)
42
43
  - [Modules](#modules)
43
44
  - [Defining a module](#defining-a-module)
@@ -82,7 +83,7 @@ f: Featurevisor = create_featurevisor({
82
83
 
83
84
  Most applications only need `create_featurevisor` and the `Featurevisor` instance type. Public extension and observability APIs include `FeaturevisorModule`, diagnostics, events, and the datafile dictionaries accepted by the factory.
84
85
 
85
- Concurrent evaluations are safe after an instance is configured. Do not mutate or close the same instance concurrently with evaluations. Serialize calls to `set_datafile`, `set_context`, `set_sticky`, `add_module`, `remove_module`, and `close`. Module, event, and diagnostic callbacks must synchronize mutable state that they capture.
86
+ Concurrent evaluations are safe after an instance is configured. Do not mutate or close the same instance concurrently with evaluations. Serialize calls to `set_datafile`, `set_context`, `set_sticky_features`, `set_sticky_variables`, `add_module`, `remove_module`, and `close`. Module, event, and diagnostic callbacks must synchronize mutable state that they capture.
86
87
 
87
88
  ## Initialization
88
89
 
@@ -106,11 +107,12 @@ f = create_featurevisor({
106
107
 
107
108
  ## Evaluation types
108
109
 
109
- We can evaluate 3 types of values against a particular [feature](https://featurevisor.com/docs/features/):
110
+ Featurevisor evaluates flags, variations, feature variables, and global variables:
110
111
 
111
112
  - [**Flag**](#check-if-enabled) (`bool`): whether the feature is enabled or not
112
113
  - [**Variation**](#getting-variation) (`string`): the variation of the feature (if any)
113
- - [**Variables**](#getting-variables): variable values of the feature (if any)
114
+ - [**Feature variables**](#getting-feature-variables): variable values owned by a feature
115
+ - [**Global variables**](#getting-global-variables): independently evaluated project values
114
116
 
115
117
  ## Context
116
118
 
@@ -169,7 +171,8 @@ You can also pass additional per-evaluation context:
169
171
  ```python
170
172
  is_enabled = f.is_enabled("my_feature", {"country": "nl"})
171
173
  variation = f.get_variation("my_feature", {"country": "nl"})
172
- variable_value = f.get_variable("my_feature", "my_variable", {"country": "nl"})
174
+ feature_variable = f.get_variable("my_feature", "my_variable", {"country": "nl"})
175
+ global_variable = f.get_variable("supportEmail", {"country": "nl"})
173
176
  ```
174
177
 
175
178
  ## Check if enabled
@@ -188,13 +191,24 @@ if variation == "treatment":
188
191
  pass
189
192
  ```
190
193
 
191
- ## Getting variables
194
+ ## Getting feature variables
192
195
 
193
196
  ```python
194
197
  bg_color = f.get_variable("my_feature", "bgColor")
195
198
  ```
196
199
 
197
- ### Type specific methods
200
+ ## Getting global variables
201
+
202
+ Global variables use the same methods without a feature key:
203
+
204
+ ```python
205
+ support_email = f.get_variable("supportEmail", {"country": "nl"})
206
+ evaluation = f.evaluate_variable("supportEmail", {"country": "nl"})
207
+ ```
208
+
209
+ Global variables honour sticky values, required features, and the first matching override. When an override contains both conditions and segments, both must match.
210
+
211
+ ## Type specific methods
198
212
 
199
213
  Typed convenience methods are also available:
200
214
 
@@ -208,33 +222,44 @@ f.get_variable_object(feature_key, variable_key, context={})
208
222
  f.get_variable_json(feature_key, variable_key, context={})
209
223
  ```
210
224
 
225
+ The same helpers evaluate global variables when the feature key is omitted:
226
+
227
+ ```python
228
+ f.get_variable_string("supportEmail", context={})
229
+ f.get_variable_object("checkoutSettings", context={})
230
+ ```
231
+
211
232
  Type specific methods do not coerce values. `get_variable_integer()` returns `None` for the string `"1"`, and boolean getters return `None` for non-boolean values.
212
233
 
213
- ## Getting all evaluations
234
+ ## Getting evaluation snapshots
214
235
 
215
236
  ```python
216
- all_evaluations = f.get_all_evaluations()
237
+ features = f.get_feature_evaluations()
238
+ variables = f.get_variable_evaluations()
217
239
  ```
218
240
 
219
- ## Sticky
241
+ ## Sticky features and variables
220
242
 
221
243
  ### Initialize with sticky
222
244
 
223
245
  You can pin feature evaluations with sticky values:
224
246
 
225
- Sticky values belong to an SDK or child instance. Evaluation options do not accept sticky overrides; use `spawn(context, {"sticky": ...})` when a child needs its own sticky state.
247
+ Sticky values belong to an SDK or child instance. Evaluation options do not accept sticky overrides. Pass `stickyFeatures` or `stickyVariables` when a child needs its own state.
226
248
 
227
249
  ```python
228
250
  f = create_featurevisor({
229
- "sticky": {
251
+ "stickyFeatures": {
230
252
  "myFeatureKey": {
231
253
  "enabled": True,
232
254
  "variation": "treatment",
233
255
  "variables": {
234
256
  "myVariableKey": "myVariableValue",
235
257
  },
236
- }
237
- }
258
+ },
259
+ },
260
+ "stickyVariables": {
261
+ "supportEmail": "sticky@example.com",
262
+ },
238
263
  })
239
264
  ```
240
265
 
@@ -243,11 +268,13 @@ f = create_featurevisor({
243
268
  Or update them later:
244
269
 
245
270
  ```python
246
- f.set_sticky({
271
+ f.set_sticky_features({
247
272
  "myFeatureKey": {
248
273
  "enabled": False,
249
274
  }
250
275
  })
276
+
277
+ f.set_sticky_variables({"supportEmail": "new@example.com"})
251
278
  ```
252
279
 
253
280
  ## Setting datafile
@@ -389,10 +416,13 @@ unsubscribe = f.on("context_set", lambda event: print(event["context"]))
389
416
  unsubscribe()
390
417
  ```
391
418
 
392
- ### `sticky_set`
419
+ ### `sticky_features_set` and `sticky_variables_set`
393
420
 
394
421
  ```python
395
- unsubscribe = f.on("sticky_set", lambda event: print(event["features"]))
422
+ unsubscribe = f.on("sticky_features_set", lambda event: print(event["features"]))
423
+ unsubscribe()
424
+
425
+ unsubscribe = f.on("sticky_variables_set", lambda event: print(event["variables"]))
396
426
  unsubscribe()
397
427
  ```
398
428
 
@@ -437,6 +467,8 @@ And optionally these properties depending on whether you are evaluating a featur
437
467
  - `variableValue`: the variable value
438
468
  - `variableSchema`: the variable schema
439
469
  - `variableOverrideIndex`: index of matched variable override when applicable
470
+ - `variableOverrideKey`: key of the matched variable override when available
471
+ - `variableOverridePath`: nested authoring path of the matched global variable override
440
472
 
441
473
  ## Modules
442
474
 
@@ -447,6 +479,8 @@ Modules can intercept evaluation and participate in SDK lifecycle:
447
479
  - `bucketKey`
448
480
  - `bucketValue`
449
481
  - `after`
482
+ - `beforeEvaluation`
483
+ - `afterEvaluation`
450
484
  - `close`
451
485
 
452
486
  ### Defining a module
@@ -456,13 +490,17 @@ my_module = {
456
490
  "name": "my-module",
457
491
  "setup": lambda api: api["onDiagnostic"](lambda diagnostic: print(diagnostic)),
458
492
  "before": lambda options: {**options, "context": {**options["context"], "country": "nl"}},
493
+ "beforeEvaluation": lambda options: options,
459
494
  "bucketKey": lambda options: options["bucketKey"],
460
495
  "bucketValue": lambda options: options["bucketValue"],
461
496
  "after": lambda evaluation, options: evaluation,
497
+ "afterEvaluation": lambda evaluation, options: evaluation,
462
498
  "close": lambda: None,
463
499
  }
464
500
  ```
465
501
 
502
+ For feature evaluations, all `before` callbacks run in registration order, followed by all `beforeEvaluation` callbacks. After evaluation and caller defaults, all `afterEvaluation` callbacks run, followed by all `after` callbacks. Global variable evaluations use only `beforeEvaluation` and `afterEvaluation`. Required feature checks run through the complete module pipeline, and transformed defaults are preserved.
503
+
466
504
  The module API passed to `setup` exposes `getRevision`, `onDiagnostic`, and `reportDiagnostic`.
467
505
 
468
506
  If `setup` raises an exception, the module is not registered. Featurevisor removes subscriptions created during setup, reports `module_setup_error`, and calls `close` when present.
@@ -493,6 +531,7 @@ child.is_enabled("my_feature")
493
531
  child.evaluate_flag("my_feature")
494
532
  child.evaluate_variation("my_feature")
495
533
  child.evaluate_variable("my_feature", "my_variable")
534
+ child.evaluate_variable("supportEmail")
496
535
  ```
497
536
 
498
537
  ## Close
@@ -571,6 +610,16 @@ python -m featurevisor benchmark \
571
610
  --context='{"userId":"123"}'
572
611
  ```
573
612
 
613
+ For a global variable, omit `--feature`:
614
+
615
+ ```bash
616
+ python -m featurevisor benchmark \
617
+ --projectDirectoryPath=/path/to/featurevisor-project \
618
+ --environment=production \
619
+ --variable=supportEmail \
620
+ --context='{"country":"nl"}'
621
+ ```
622
+
574
623
  ### Assess distribution
575
624
 
576
625
  Inspect enabled/disabled and variation distribution over repeated evaluations:
@@ -634,6 +683,7 @@ Call `api.shutdown()` during application shutdown. This closes a Featurevisor in
634
683
  | `checkout` | Boolean flag for `checkout` |
635
684
  | `checkout:variation` | Variation value for `checkout` |
636
685
  | `checkout:title` | Variable `title` for `checkout` |
686
+ | `variable:supportEmail` | Global variable `supportEmail` |
637
687
 
638
688
  Boolean variables use the boolean resolver. Integer and double variables use their matching numeric resolvers. Arrays, objects, and JSON variables use the object resolver.
639
689
 
@@ -644,10 +694,11 @@ provider = FeaturevisorOpenFeatureProvider(
644
694
  {"datafile": datafile_content},
645
695
  key_separator="/",
646
696
  variation_key="$variation",
697
+ global_variable_prefix="$variable",
647
698
  )
648
699
  ```
649
700
 
650
- This makes `checkout/$variation` the variation key and `checkout/title` a variable key.
701
+ This makes `checkout/$variation` the variation key, `checkout/title` a feature variable key, and `$variable/supportEmail` a global variable key. The global prefix defaults to `variable` and cannot contain the separator.
651
702
 
652
703
  ### Context mapping
653
704
 
@@ -668,9 +719,9 @@ The provider maps Featurevisor evaluation results to OpenFeature details:
668
719
 
669
720
  | Featurevisor result | OpenFeature result |
670
721
  | --- | --- |
671
- | Required, forced, sticky, or rule match | `TARGETING_MATCH` |
722
+ | Required feature rule, forced, sticky, or rule match | `TARGETING_MATCH` |
672
723
  | Traffic allocation | `SPLIT` |
673
- | Disabled variation or variable | `DISABLED` |
724
+ | Unmet global variable requirements, disabled variation, or disabled variable | `DISABLED` |
674
725
  | No match or variable default | `DEFAULT` |
675
726
  | Missing feature, variable, or variations | `ERROR` with `FLAG_NOT_FOUND` |
676
727
  | Wrong resolver type | `ERROR` with `TYPE_MISMATCH` |
@@ -679,7 +730,7 @@ The provider maps Featurevisor evaluation results to OpenFeature details:
679
730
 
680
731
  Errors return the default value supplied to OpenFeature. A malformed datafile uses the stable message `Could not parse datafile`. A later successful `set_datafile` call clears the parse error.
681
732
 
682
- Resolution metadata can include `featureKey`, `variableKey`, `featurevisorReason`, `revision`, `schemaVersion`, `ruleKey`, `bucketKey`, `bucketValue`, `forceIndex`, and `variableOverrideIndex`. The selected variation is exposed as the OpenFeature variant when available.
733
+ Resolution metadata can include `featureKey`, `variableKey`, `featurevisorReason`, `revision`, `schemaVersion`, `ruleKey`, `bucketKey`, `bucketValue`, `forceIndex`, `variableOverrideIndex`, and `variableOverrideKey`. The selected variation is exposed as the OpenFeature variant when available.
683
734
 
684
735
  ### Tracking
685
736