switchbox-flags 0.4.0__tar.gz → 0.5.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 (23) hide show
  1. {switchbox_flags-0.4.0 → switchbox_flags-0.5.0}/PKG-INFO +1 -1
  2. {switchbox_flags-0.4.0 → switchbox_flags-0.5.0}/pyproject.toml +1 -1
  3. {switchbox_flags-0.4.0 → switchbox_flags-0.5.0}/switchbox/__init__.py +1 -1
  4. {switchbox_flags-0.4.0 → switchbox_flags-0.5.0}/switchbox/evaluator.py +8 -2
  5. {switchbox_flags-0.4.0 → switchbox_flags-0.5.0}/switchbox/models.py +5 -0
  6. {switchbox_flags-0.4.0 → switchbox_flags-0.5.0}/tests/parity_vectors.json +7 -1
  7. {switchbox_flags-0.4.0 → switchbox_flags-0.5.0}/uv.lock +1 -1
  8. {switchbox_flags-0.4.0 → switchbox_flags-0.5.0}/.coverage +0 -0
  9. {switchbox_flags-0.4.0 → switchbox_flags-0.5.0}/.github/workflows/publish.yml +0 -0
  10. {switchbox_flags-0.4.0 → switchbox_flags-0.5.0}/.github/workflows/test.yml +0 -0
  11. {switchbox_flags-0.4.0 → switchbox_flags-0.5.0}/.gitignore +0 -0
  12. {switchbox_flags-0.4.0 → switchbox_flags-0.5.0}/LICENSE +0 -0
  13. {switchbox_flags-0.4.0 → switchbox_flags-0.5.0}/README.md +0 -0
  14. {switchbox_flags-0.4.0 → switchbox_flags-0.5.0}/switchbox/cache.py +0 -0
  15. {switchbox_flags-0.4.0 → switchbox_flags-0.5.0}/switchbox/client.py +0 -0
  16. {switchbox_flags-0.4.0 → switchbox_flags-0.5.0}/switchbox/exceptions.py +0 -0
  17. {switchbox_flags-0.4.0 → switchbox_flags-0.5.0}/switchbox/sync.py +0 -0
  18. {switchbox_flags-0.4.0 → switchbox_flags-0.5.0}/tests/__init__.py +0 -0
  19. {switchbox_flags-0.4.0 → switchbox_flags-0.5.0}/tests/test_cache.py +0 -0
  20. {switchbox_flags-0.4.0 → switchbox_flags-0.5.0}/tests/test_client.py +0 -0
  21. {switchbox_flags-0.4.0 → switchbox_flags-0.5.0}/tests/test_evaluator.py +0 -0
  22. {switchbox_flags-0.4.0 → switchbox_flags-0.5.0}/tests/test_models.py +0 -0
  23. {switchbox_flags-0.4.0 → switchbox_flags-0.5.0}/tests/test_parity_vectors.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: switchbox-flags
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Feature flag SDK with zero dependencies
5
5
  Project-URL: Homepage, https://github.com/ignat14/switchbox-sdk-python
6
6
  Project-URL: Repository, https://github.com/ignat14/switchbox-sdk-python
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "switchbox-flags"
7
- version = "0.4.0"
7
+ version = "0.5.0"
8
8
  description = "Feature flag SDK with zero dependencies"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,5 +1,5 @@
1
1
  from switchbox.client import Switchbox
2
2
  from switchbox.exceptions import SwitchboxError
3
3
 
4
- __version__ = "0.4.0"
4
+ __version__ = "0.5.0"
5
5
  __all__ = ["Switchbox", "SwitchboxError"]
@@ -88,10 +88,16 @@ def evaluate(flag: Flag, user_context: dict | None = None) -> bool | str | int |
88
88
 
89
89
 
90
90
  def _enabled_value(flag: Flag) -> bool | str | int | Any:
91
- """Return the appropriate 'enabled' value based on flag type."""
91
+ """The value served to matched / in-rollout users.
92
+
93
+ Boolean flags serve ``True``. Non-boolean flags serve ``enabled_value``
94
+ (variations, ADR-017), falling back to ``default_value`` when it's unset —
95
+ so a config without ``enabled_value`` behaves exactly as before. Fallback is
96
+ at evaluation time (not parse time) so directly-constructed Flags work too,
97
+ matching the JS SDK's ``?? default_value``."""
92
98
  if flag.flag_type == "boolean":
93
99
  return True
94
- return flag.default_value
100
+ return flag.enabled_value if flag.enabled_value is not None else flag.default_value
95
101
 
96
102
 
97
103
  def _to_groups(rules: list) -> list[RuleGroup]:
@@ -29,6 +29,10 @@ class Flag:
29
29
  rollout_pct: int
30
30
  flag_type: str # boolean | string | number | json
31
31
  default_value: Any
32
+ # The "on"/matched value for non-boolean flags (variations, ADR-017). None
33
+ # means "unset" — the evaluator falls back to default_value, so configs
34
+ # without this key behave exactly as before. Boolean flags ignore it.
35
+ enabled_value: Any = None
32
36
  rules: list[RuleGroup] = field(default_factory=list)
33
37
 
34
38
 
@@ -62,6 +66,7 @@ class FlagConfig:
62
66
  rollout_pct=flag_data.get("rollout_pct", 0),
63
67
  flag_type=flag_data.get("flag_type", "boolean"),
64
68
  default_value=flag_data.get("default_value"),
69
+ enabled_value=flag_data.get("enabled_value"),
65
70
  rules=rules,
66
71
  )
67
72
  return cls(version=data.get("version", ""), flags=flags)
@@ -34,6 +34,12 @@
34
34
  {"name": "DNF: second group matches (OR across groups) -> enabled", "flag_key": "f", "flag": {"enabled": true, "rollout_pct": 0, "flag_type": "boolean", "default_value": false, "rules": [{"conditions": [{"attribute": "country", "operator": "equals", "value": "US"}]}, {"conditions": [{"attribute": "plan", "operator": "equals", "value": "ent"}]}]}, "user": {"plan": "ent"}, "expected": true},
35
35
  {"name": "DNF: no group matches (OR across groups) -> default", "flag_key": "f", "flag": {"enabled": true, "rollout_pct": 0, "flag_type": "boolean", "default_value": false, "rules": [{"conditions": [{"attribute": "country", "operator": "equals", "value": "US"}]}, {"conditions": [{"attribute": "plan", "operator": "equals", "value": "ent"}]}]}, "user": {"plan": "free", "country": "EU"}, "expected": false},
36
36
  {"name": "DNF: empty-conditions group never matches -> default", "flag_key": "f", "flag": {"enabled": true, "rollout_pct": 0, "flag_type": "boolean", "default_value": false, "rules": [{"conditions": []}]}, "user": {"any": "thing"}, "expected": false},
37
- {"name": "DNF back-compat: legacy flat rule still matches -> enabled", "flag_key": "f", "flag": {"enabled": true, "rollout_pct": 0, "flag_type": "boolean", "default_value": false, "rules": [{"attribute": "plan", "operator": "equals", "value": "pro"}]}, "user": {"plan": "pro"}, "expected": true}
37
+ {"name": "DNF back-compat: legacy flat rule still matches -> enabled", "flag_key": "f", "flag": {"enabled": true, "rollout_pct": 0, "flag_type": "boolean", "default_value": false, "rules": [{"attribute": "plan", "operator": "equals", "value": "pro"}]}, "user": {"plan": "pro"}, "expected": true},
38
+ {"name": "variation: string flag rule match serves enabled_value", "flag_key": "f", "flag": {"enabled": true, "rollout_pct": 0, "flag_type": "string", "default_value": "Shop", "enabled_value": "Buy now", "rules": [{"conditions": [{"attribute": "plan", "operator": "equals", "value": "pro"}]}]}, "user": {"plan": "pro", "user_id": "u1"}, "expected": "Buy now"},
39
+ {"name": "variation: string flag no match serves default_value", "flag_key": "f", "flag": {"enabled": true, "rollout_pct": 0, "flag_type": "string", "default_value": "Shop", "enabled_value": "Buy now", "rules": [{"conditions": [{"attribute": "plan", "operator": "equals", "value": "pro"}]}]}, "user": {"plan": "free", "user_id": "u1"}, "expected": "Shop"},
40
+ {"name": "variation: absent enabled_value falls back to default on match", "flag_key": "f", "flag": {"enabled": true, "rollout_pct": 0, "flag_type": "string", "default_value": "Shop", "rules": [{"conditions": [{"attribute": "plan", "operator": "equals", "value": "pro"}]}]}, "user": {"plan": "pro", "user_id": "u1"}, "expected": "Shop"},
41
+ {"name": "variation: number flag rollout 100 no context serves enabled_value", "flag_key": "f", "flag": {"enabled": true, "rollout_pct": 100, "flag_type": "number", "default_value": 0, "enabled_value": 42, "rules": []}, "user": null, "expected": 42},
42
+ {"name": "variation: disabled non-boolean serves default_value", "flag_key": "f", "flag": {"enabled": false, "rollout_pct": 100, "flag_type": "string", "default_value": "Shop", "enabled_value": "Buy now", "rules": []}, "user": {"user_id": "u1"}, "expected": "Shop"},
43
+ {"name": "variation: boolean flag ignores enabled_value (serves true)", "flag_key": "f", "flag": {"enabled": true, "rollout_pct": 100, "flag_type": "boolean", "default_value": false, "enabled_value": "ignored", "rules": []}, "user": null, "expected": true}
38
44
  ]
39
45
  }
@@ -236,7 +236,7 @@ wheels = [
236
236
 
237
237
  [[package]]
238
238
  name = "switchbox-flags"
239
- version = "0.4.0"
239
+ version = "0.5.0"
240
240
  source = { editable = "." }
241
241
 
242
242
  [package.optional-dependencies]
File without changes