envflags 0.1.0__tar.gz → 0.2.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.
- {envflags-0.1.0 → envflags-0.2.0}/PKG-INFO +5 -2
- {envflags-0.1.0 → envflags-0.2.0}/README.md +4 -1
- {envflags-0.1.0 → envflags-0.2.0}/envflags/client.py +24 -9
- {envflags-0.1.0 → envflags-0.2.0}/envflags/evaluate.py +13 -11
- {envflags-0.1.0 → envflags-0.2.0}/envflags.egg-info/PKG-INFO +5 -2
- {envflags-0.1.0 → envflags-0.2.0}/pyproject.toml +1 -1
- {envflags-0.1.0 → envflags-0.2.0}/tests/test_client.py +8 -7
- {envflags-0.1.0 → envflags-0.2.0}/envflags/__init__.py +0 -0
- {envflags-0.1.0 → envflags-0.2.0}/envflags.egg-info/SOURCES.txt +0 -0
- {envflags-0.1.0 → envflags-0.2.0}/envflags.egg-info/dependency_links.txt +0 -0
- {envflags-0.1.0 → envflags-0.2.0}/envflags.egg-info/top_level.txt +0 -0
- {envflags-0.1.0 → envflags-0.2.0}/setup.cfg +0 -0
- {envflags-0.1.0 → envflags-0.2.0}/tests/test_evaluate.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: envflags
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Python SDK for envflags
|
|
5
5
|
Author: envflags
|
|
6
6
|
License: MIT
|
|
@@ -21,13 +21,16 @@ flags = create_client("envflags_server_...").initialize()
|
|
|
21
21
|
try:
|
|
22
22
|
enabled = flags.bool_variation(
|
|
23
23
|
"new-checkout",
|
|
24
|
-
{"key": "user-123", "country": "BE"},
|
|
25
24
|
False,
|
|
25
|
+
{"key": "user-123", "country": "BE"},
|
|
26
26
|
)
|
|
27
27
|
finally:
|
|
28
28
|
flags.close()
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
Context is optional. Omit it for flags that do not depend on who is asking; pass
|
|
32
|
+
it last when targeting needs a user.
|
|
33
|
+
|
|
31
34
|
The client polls every 30 seconds and fetches a payload only when its version
|
|
32
35
|
changes. Set `poll_interval=None` to refresh manually. Supply a previously
|
|
33
36
|
persisted payload through `bootstrap` to remain available when initialization
|
|
@@ -10,13 +10,16 @@ flags = create_client("envflags_server_...").initialize()
|
|
|
10
10
|
try:
|
|
11
11
|
enabled = flags.bool_variation(
|
|
12
12
|
"new-checkout",
|
|
13
|
-
{"key": "user-123", "country": "BE"},
|
|
14
13
|
False,
|
|
14
|
+
{"key": "user-123", "country": "BE"},
|
|
15
15
|
)
|
|
16
16
|
finally:
|
|
17
17
|
flags.close()
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
+
Context is optional. Omit it for flags that do not depend on who is asking; pass
|
|
21
|
+
it last when targeting needs a user.
|
|
22
|
+
|
|
20
23
|
The client polls every 30 seconds and fetches a payload only when its version
|
|
21
24
|
changes. Set `poll_interval=None` to refresh manually. Supply a previously
|
|
22
25
|
persisted payload through `bootstrap` to remain available when initialization
|
|
@@ -121,7 +121,10 @@ class Client:
|
|
|
121
121
|
thread.join()
|
|
122
122
|
|
|
123
123
|
def variation(
|
|
124
|
-
self,
|
|
124
|
+
self,
|
|
125
|
+
flag_key: str,
|
|
126
|
+
default_value: T,
|
|
127
|
+
context: Optional[Context] = None,
|
|
125
128
|
) -> Any:
|
|
126
129
|
with self._condition:
|
|
127
130
|
payload = self._payload
|
|
@@ -130,21 +133,30 @@ class Client:
|
|
|
130
133
|
return evaluate(payload, flag_key, context, default_value)
|
|
131
134
|
|
|
132
135
|
def bool_variation(
|
|
133
|
-
self,
|
|
136
|
+
self,
|
|
137
|
+
flag_key: str,
|
|
138
|
+
default_value: bool,
|
|
139
|
+
context: Optional[Context] = None,
|
|
134
140
|
) -> bool:
|
|
135
|
-
value = self.variation(flag_key,
|
|
141
|
+
value = self.variation(flag_key, default_value, context)
|
|
136
142
|
return value if isinstance(value, bool) else default_value
|
|
137
143
|
|
|
138
144
|
def string_variation(
|
|
139
|
-
self,
|
|
145
|
+
self,
|
|
146
|
+
flag_key: str,
|
|
147
|
+
default_value: str,
|
|
148
|
+
context: Optional[Context] = None,
|
|
140
149
|
) -> str:
|
|
141
|
-
value = self.variation(flag_key,
|
|
150
|
+
value = self.variation(flag_key, default_value, context)
|
|
142
151
|
return value if isinstance(value, str) else default_value
|
|
143
152
|
|
|
144
153
|
def number_variation(
|
|
145
|
-
self,
|
|
154
|
+
self,
|
|
155
|
+
flag_key: str,
|
|
156
|
+
default_value: float,
|
|
157
|
+
context: Optional[Context] = None,
|
|
146
158
|
) -> float:
|
|
147
|
-
value = self.variation(flag_key,
|
|
159
|
+
value = self.variation(flag_key, default_value, context)
|
|
148
160
|
if (
|
|
149
161
|
isinstance(value, bool)
|
|
150
162
|
or not isinstance(value, (int, float))
|
|
@@ -154,9 +166,12 @@ class Client:
|
|
|
154
166
|
return float(value)
|
|
155
167
|
|
|
156
168
|
def json_variation(
|
|
157
|
-
self,
|
|
169
|
+
self,
|
|
170
|
+
flag_key: str,
|
|
171
|
+
default_value: T,
|
|
172
|
+
context: Optional[Context] = None,
|
|
158
173
|
) -> Any:
|
|
159
|
-
return self.variation(flag_key,
|
|
174
|
+
return self.variation(flag_key, default_value, context)
|
|
160
175
|
|
|
161
176
|
def _perform_refresh(self) -> bool:
|
|
162
177
|
with self._condition:
|
|
@@ -25,22 +25,23 @@ def bucket(flag_key: str, salt: str, bucket_by: str) -> float:
|
|
|
25
25
|
def evaluate(
|
|
26
26
|
payload: Payload,
|
|
27
27
|
flag_key: str,
|
|
28
|
-
context: Context,
|
|
28
|
+
context: Optional[Context],
|
|
29
29
|
default_value: Any,
|
|
30
30
|
) -> Any:
|
|
31
31
|
"""Evaluate one flag locally, falling back when no variation is valid."""
|
|
32
32
|
flags = payload.get("flags")
|
|
33
33
|
flag = flags.get(flag_key) if isinstance(flags, Mapping) else None
|
|
34
|
-
|
|
35
|
-
if not isinstance(flag, Mapping) or not isinstance(key, str) or not key:
|
|
34
|
+
if not isinstance(flag, Mapping):
|
|
36
35
|
return default_value
|
|
36
|
+
resolved: Mapping[str, Any] = {} if context is None else context
|
|
37
|
+
key = resolved.get("key")
|
|
37
38
|
|
|
38
39
|
variation: Optional[int] = None
|
|
39
40
|
if flag.get("on") is not True:
|
|
40
41
|
variation = _index(flag.get("offVariation"))
|
|
41
42
|
else:
|
|
42
43
|
targets = flag.get("targets")
|
|
43
|
-
if isinstance(targets, list):
|
|
44
|
+
if isinstance(key, str) and key and isinstance(targets, list):
|
|
44
45
|
for target in targets:
|
|
45
46
|
if not isinstance(target, Mapping):
|
|
46
47
|
continue
|
|
@@ -57,10 +58,10 @@ def evaluate(
|
|
|
57
58
|
continue
|
|
58
59
|
clauses = rule.get("clauses")
|
|
59
60
|
if isinstance(clauses, list) and _clauses_match(
|
|
60
|
-
clauses,
|
|
61
|
+
clauses, resolved, payload, set()
|
|
61
62
|
):
|
|
62
63
|
variation = _select_variation(
|
|
63
|
-
rule, flag_key,
|
|
64
|
+
rule, flag_key, resolved, payload
|
|
64
65
|
)
|
|
65
66
|
break
|
|
66
67
|
|
|
@@ -68,7 +69,7 @@ def evaluate(
|
|
|
68
69
|
fallthrough = flag.get("fallthrough")
|
|
69
70
|
if isinstance(fallthrough, Mapping):
|
|
70
71
|
variation = _select_variation(
|
|
71
|
-
fallthrough, flag_key,
|
|
72
|
+
fallthrough, flag_key, resolved, payload
|
|
72
73
|
)
|
|
73
74
|
|
|
74
75
|
variations = flag.get("variations")
|
|
@@ -245,11 +246,12 @@ def _segment_matches(
|
|
|
245
246
|
) -> bool:
|
|
246
247
|
key = context.get("key")
|
|
247
248
|
excluded = segment.get("excluded")
|
|
248
|
-
if isinstance(excluded, list) and key in excluded:
|
|
249
|
-
return False
|
|
250
249
|
included = segment.get("included")
|
|
251
|
-
if isinstance(
|
|
252
|
-
|
|
250
|
+
if isinstance(key, str) and key:
|
|
251
|
+
if isinstance(excluded, list) and key in excluded:
|
|
252
|
+
return False
|
|
253
|
+
if isinstance(included, list) and key in included:
|
|
254
|
+
return True
|
|
253
255
|
rules = segment.get("rules")
|
|
254
256
|
if not isinstance(rules, list):
|
|
255
257
|
return False
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: envflags
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Python SDK for envflags
|
|
5
5
|
Author: envflags
|
|
6
6
|
License: MIT
|
|
@@ -21,13 +21,16 @@ flags = create_client("envflags_server_...").initialize()
|
|
|
21
21
|
try:
|
|
22
22
|
enabled = flags.bool_variation(
|
|
23
23
|
"new-checkout",
|
|
24
|
-
{"key": "user-123", "country": "BE"},
|
|
25
24
|
False,
|
|
25
|
+
{"key": "user-123", "country": "BE"},
|
|
26
26
|
)
|
|
27
27
|
finally:
|
|
28
28
|
flags.close()
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
Context is optional. Omit it for flags that do not depend on who is asking; pass
|
|
32
|
+
it last when targeting needs a user.
|
|
33
|
+
|
|
31
34
|
The client polls every 30 seconds and fetches a payload only when its version
|
|
32
35
|
changes. Set `poll_interval=None` to refresh manually. Supply a previously
|
|
33
36
|
persisted payload through `bootstrap` to remain available when initialization
|
|
@@ -90,11 +90,12 @@ class ClientTest(unittest.TestCase):
|
|
|
90
90
|
self.assertIs(client.initialize(), client)
|
|
91
91
|
self.assertTrue(client.initialized)
|
|
92
92
|
self.assertEqual(client.version, 3)
|
|
93
|
-
self.assertTrue(client.bool_variation("enabled", {"key": "user"}
|
|
93
|
+
self.assertTrue(client.bool_variation("enabled", False, {"key": "user"}))
|
|
94
94
|
self.assertEqual(
|
|
95
|
-
client.string_variation("greeting", {"key": "user"}
|
|
95
|
+
client.string_variation("greeting", "fallback", {"key": "user"}),
|
|
96
96
|
"hello",
|
|
97
97
|
)
|
|
98
|
+
self.assertTrue(client.bool_variation("enabled", False))
|
|
98
99
|
self.assertEqual(opener.requests[1].full_url, "https://edge.example.test/sdk/payloads/3")
|
|
99
100
|
|
|
100
101
|
self.assertFalse(client.refresh())
|
|
@@ -121,7 +122,7 @@ class ClientTest(unittest.TestCase):
|
|
|
121
122
|
)
|
|
122
123
|
self.assertIs(client.initialize(), client)
|
|
123
124
|
self.assertEqual(len(errors), 1)
|
|
124
|
-
self.assertTrue(client.bool_variation("enabled", {"key": "user"}
|
|
125
|
+
self.assertTrue(client.bool_variation("enabled", False, {"key": "user"}))
|
|
125
126
|
|
|
126
127
|
def test_rejects_offline_initialization_without_payload(self):
|
|
127
128
|
client = Client(
|
|
@@ -132,17 +133,17 @@ class ClientTest(unittest.TestCase):
|
|
|
132
133
|
with self.assertRaisesRegex(EnvflagsError, "offline"):
|
|
133
134
|
client.initialize()
|
|
134
135
|
self.assertFalse(
|
|
135
|
-
client.bool_variation("enabled", {"key": "user"}
|
|
136
|
+
client.bool_variation("enabled", False, {"key": "user"})
|
|
136
137
|
)
|
|
137
138
|
|
|
138
139
|
def test_returns_typed_defaults(self):
|
|
139
140
|
client = Client("client_test", bootstrap=payload(), poll_interval=None)
|
|
140
141
|
context = {"key": "user"}
|
|
141
|
-
self.assertFalse(client.bool_variation("greeting",
|
|
142
|
+
self.assertFalse(client.bool_variation("greeting", False, context))
|
|
142
143
|
self.assertEqual(
|
|
143
|
-
client.string_variation("missing",
|
|
144
|
+
client.string_variation("missing", "fallback", context), "fallback"
|
|
144
145
|
)
|
|
145
|
-
self.assertEqual(client.number_variation("enabled",
|
|
146
|
+
self.assertEqual(client.number_variation("enabled", 42, context), 42)
|
|
146
147
|
|
|
147
148
|
def test_coalesces_concurrent_refreshes(self):
|
|
148
149
|
started = threading.Event()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|