growthbook 2.1.1__py2.py3-none-any.whl → 2.1.3__py2.py3-none-any.whl
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.
- growthbook/__init__.py +1 -1
- growthbook/core.py +30 -29
- growthbook/growthbook_client.py +2 -2
- {growthbook-2.1.1.dist-info → growthbook-2.1.3.dist-info}/METADATA +1 -1
- {growthbook-2.1.1.dist-info → growthbook-2.1.3.dist-info}/RECORD +8 -8
- {growthbook-2.1.1.dist-info → growthbook-2.1.3.dist-info}/WHEEL +0 -0
- {growthbook-2.1.1.dist-info → growthbook-2.1.3.dist-info}/licenses/LICENSE +0 -0
- {growthbook-2.1.1.dist-info → growthbook-2.1.3.dist-info}/top_level.txt +0 -0
growthbook/__init__.py
CHANGED
growthbook/core.py
CHANGED
|
@@ -50,47 +50,48 @@ def isOperatorObject(obj: Any) -> bool:
|
|
|
50
50
|
return False
|
|
51
51
|
return True
|
|
52
52
|
|
|
53
|
-
def
|
|
54
|
-
|
|
53
|
+
def _is_numeric(v: Any) -> bool:
|
|
54
|
+
return isinstance(v, (int, float)) and not isinstance(v, bool)
|
|
55
55
|
|
|
56
|
+
def getType(attributeValue) -> str:
|
|
56
57
|
if attributeValue is None:
|
|
57
58
|
return "null"
|
|
58
|
-
if
|
|
59
|
+
if isinstance(attributeValue, bool):
|
|
60
|
+
return "boolean"
|
|
61
|
+
if _is_numeric(attributeValue):
|
|
59
62
|
return "number"
|
|
60
|
-
if
|
|
63
|
+
if isinstance(attributeValue, str):
|
|
61
64
|
return "string"
|
|
62
|
-
if
|
|
65
|
+
if isinstance(attributeValue, (list, set)):
|
|
63
66
|
return "array"
|
|
64
|
-
if
|
|
67
|
+
if isinstance(attributeValue, dict):
|
|
65
68
|
return "object"
|
|
66
|
-
if t is bool:
|
|
67
|
-
return "boolean"
|
|
68
69
|
return "unknown"
|
|
69
70
|
|
|
70
71
|
def getPath(attributes, path):
|
|
71
72
|
current = attributes
|
|
72
73
|
for segment in path.split("."):
|
|
73
|
-
if
|
|
74
|
+
if isinstance(current, dict) and segment in current:
|
|
74
75
|
current = current[segment]
|
|
75
76
|
else:
|
|
76
77
|
return None
|
|
77
78
|
return current
|
|
78
79
|
|
|
79
80
|
def evalConditionValue(conditionValue, attributeValue, savedGroups, insensitive: bool = False) -> bool:
|
|
80
|
-
if
|
|
81
|
+
if isinstance(conditionValue, dict) and isOperatorObject(conditionValue):
|
|
81
82
|
for key, value in conditionValue.items():
|
|
82
83
|
if not evalOperatorCondition(key, attributeValue, value, savedGroups):
|
|
83
84
|
return False
|
|
84
85
|
return True
|
|
85
86
|
|
|
86
87
|
# Simple equality comparison with optional case-insensitivity
|
|
87
|
-
if insensitive and
|
|
88
|
+
if insensitive and isinstance(conditionValue, str) and isinstance(attributeValue, str):
|
|
88
89
|
return conditionValue.lower() == attributeValue.lower()
|
|
89
90
|
|
|
90
91
|
return bool(conditionValue == attributeValue)
|
|
91
92
|
|
|
92
93
|
def elemMatch(condition, attributeValue, savedGroups) -> bool:
|
|
93
|
-
if not
|
|
94
|
+
if not isinstance(attributeValue, list):
|
|
94
95
|
return False
|
|
95
96
|
|
|
96
97
|
for item in attributeValue:
|
|
@@ -104,13 +105,13 @@ def elemMatch(condition, attributeValue, savedGroups) -> bool:
|
|
|
104
105
|
return False
|
|
105
106
|
|
|
106
107
|
def compare(val1, val2) -> int:
|
|
107
|
-
if (
|
|
108
|
+
if _is_numeric(val1) and not _is_numeric(val2):
|
|
108
109
|
if (val2 is None):
|
|
109
110
|
val2 = 0
|
|
110
111
|
else:
|
|
111
112
|
val2 = float(val2)
|
|
112
113
|
|
|
113
|
-
if (
|
|
114
|
+
if _is_numeric(val2) and not _is_numeric(val1):
|
|
114
115
|
if (val1 is None):
|
|
115
116
|
val1 = 0
|
|
116
117
|
else:
|
|
@@ -166,13 +167,13 @@ def evalOperatorCondition(operator, attributeValue, conditionValue, savedGroups)
|
|
|
166
167
|
elif operator == "$vgte":
|
|
167
168
|
return paddedVersionString(attributeValue) >= paddedVersionString(conditionValue)
|
|
168
169
|
elif operator == "$inGroup":
|
|
169
|
-
if not
|
|
170
|
+
if not isinstance(conditionValue, str):
|
|
170
171
|
return False
|
|
171
172
|
if not conditionValue in savedGroups:
|
|
172
173
|
return False
|
|
173
174
|
return isIn(savedGroups[conditionValue] or [], attributeValue)
|
|
174
175
|
elif operator == "$notInGroup":
|
|
175
|
-
if not
|
|
176
|
+
if not isinstance(conditionValue, str):
|
|
176
177
|
return False
|
|
177
178
|
if not conditionValue in savedGroups:
|
|
178
179
|
return True
|
|
@@ -202,33 +203,33 @@ def evalOperatorCondition(operator, attributeValue, conditionValue, savedGroups)
|
|
|
202
203
|
except Exception:
|
|
203
204
|
return False
|
|
204
205
|
elif operator == "$in":
|
|
205
|
-
if not
|
|
206
|
+
if not isinstance(conditionValue, list):
|
|
206
207
|
return False
|
|
207
208
|
return isIn(conditionValue, attributeValue)
|
|
208
209
|
elif operator == "$nin":
|
|
209
|
-
if not
|
|
210
|
+
if not isinstance(conditionValue, list):
|
|
210
211
|
return False
|
|
211
212
|
return not isIn(conditionValue, attributeValue)
|
|
212
213
|
elif operator == "$ini":
|
|
213
|
-
if not
|
|
214
|
+
if not isinstance(conditionValue, list):
|
|
214
215
|
return False
|
|
215
216
|
return isIn(conditionValue, attributeValue, insensitive=True)
|
|
216
217
|
elif operator == "$nini":
|
|
217
|
-
if not
|
|
218
|
+
if not isinstance(conditionValue, list):
|
|
218
219
|
return False
|
|
219
220
|
return not isIn(conditionValue, attributeValue, insensitive=True)
|
|
220
221
|
elif operator == "$elemMatch":
|
|
221
222
|
return elemMatch(conditionValue, attributeValue, savedGroups)
|
|
222
223
|
elif operator == "$size":
|
|
223
|
-
if not (
|
|
224
|
+
if not isinstance(attributeValue, list):
|
|
224
225
|
return False
|
|
225
226
|
return evalConditionValue(conditionValue, len(attributeValue), savedGroups)
|
|
226
227
|
elif operator == "$all":
|
|
227
|
-
if not
|
|
228
|
+
if not isinstance(conditionValue, list):
|
|
228
229
|
return False
|
|
229
230
|
return isInAll(conditionValue, attributeValue, savedGroups, insensitive=False)
|
|
230
231
|
elif operator == "$alli":
|
|
231
|
-
if not
|
|
232
|
+
if not isinstance(conditionValue, list):
|
|
232
233
|
return False
|
|
233
234
|
return isInAll(conditionValue, attributeValue, savedGroups, insensitive=True)
|
|
234
235
|
elif operator == "$exists":
|
|
@@ -243,10 +244,10 @@ def evalOperatorCondition(operator, attributeValue, conditionValue, savedGroups)
|
|
|
243
244
|
|
|
244
245
|
def paddedVersionString(input) -> str:
|
|
245
246
|
# If input is a number, convert to a string
|
|
246
|
-
if
|
|
247
|
+
if _is_numeric(input):
|
|
247
248
|
input = str(input)
|
|
248
249
|
|
|
249
|
-
if not input or
|
|
250
|
+
if not input or not isinstance(input, str):
|
|
250
251
|
input = "0"
|
|
251
252
|
|
|
252
253
|
# Remove build info and leading `v` if any
|
|
@@ -268,10 +269,10 @@ def isIn(conditionValue, attributeValue, insensitive: bool = False) -> bool:
|
|
|
268
269
|
if insensitive:
|
|
269
270
|
# Helper function to case-fold values (lowercase for strings)
|
|
270
271
|
def case_fold(val):
|
|
271
|
-
return val.lower() if
|
|
272
|
+
return val.lower() if isinstance(val, str) else val
|
|
272
273
|
|
|
273
274
|
# Do an intersection if attribute is an array (insensitive)
|
|
274
|
-
if
|
|
275
|
+
if isinstance(attributeValue, list):
|
|
275
276
|
return any(
|
|
276
277
|
case_fold(el) == case_fold(exp)
|
|
277
278
|
for el in attributeValue
|
|
@@ -280,13 +281,13 @@ def isIn(conditionValue, attributeValue, insensitive: bool = False) -> bool:
|
|
|
280
281
|
return any(case_fold(attributeValue) == case_fold(exp) for exp in conditionValue)
|
|
281
282
|
|
|
282
283
|
# Case-sensitive behavior (original)
|
|
283
|
-
if
|
|
284
|
+
if isinstance(attributeValue, list):
|
|
284
285
|
return bool(set(conditionValue) & set(attributeValue))
|
|
285
286
|
return attributeValue in conditionValue
|
|
286
287
|
|
|
287
288
|
def isInAll(conditionValue, attributeValue, savedGroups, insensitive: bool = False) -> bool:
|
|
288
289
|
"""Check if attributeValue (array) contains all elements in conditionValue"""
|
|
289
|
-
if not
|
|
290
|
+
if not isinstance(attributeValue, list):
|
|
290
291
|
return False
|
|
291
292
|
|
|
292
293
|
for cond in conditionValue:
|
growthbook/growthbook_client.py
CHANGED
|
@@ -90,8 +90,8 @@ class FeatureCache:
|
|
|
90
90
|
def update(self, features: Dict[str, Any], saved_groups: Dict[str, Any]) -> None:
|
|
91
91
|
"""Simple thread-safe update of cache with new API data"""
|
|
92
92
|
with self._lock:
|
|
93
|
-
self._cache['features']
|
|
94
|
-
self._cache['savedGroups']
|
|
93
|
+
self._cache['features'] = dict(features)
|
|
94
|
+
self._cache['savedGroups'] = dict(saved_groups)
|
|
95
95
|
|
|
96
96
|
def get_current_state(self) -> Dict[str, Any]:
|
|
97
97
|
"""Get current cache state"""
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
growthbook/__init__.py,sha256=
|
|
1
|
+
growthbook/__init__.py,sha256=Ruoqw2MnuEXLWduM7wtAINQjemYc3AEzdtGUSXGthWA,444
|
|
2
2
|
growthbook/common_types.py,sha256=YKUmmYfzgrzLQ7kp2IPLc8QBA-B0QbnbF5viekNiTpw,15703
|
|
3
|
-
growthbook/core.py,sha256=
|
|
3
|
+
growthbook/core.py,sha256=bxvzu_YhuSZzQpEgdR4uArl8ruNsmBZN3maURd8V65E,38425
|
|
4
4
|
growthbook/growthbook.py,sha256=Ee6-jWPtvlgRvxRbsX-ZhrMV-8_F2T78Q7P30DVMsQM,47833
|
|
5
|
-
growthbook/growthbook_client.py,sha256=
|
|
5
|
+
growthbook/growthbook_client.py,sha256=rIjIoWu4L-kwpBDSJrQxEX2hJ7Gdk3yR5lzMmqW6F-4,27578
|
|
6
6
|
growthbook/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
growthbook/plugins/__init__.py,sha256=y2eAV1sA041XWcftBVTDH0t-ggy9r2C5oKRYRF6XR6s,602
|
|
8
8
|
growthbook/plugins/base.py,sha256=PWBXUBj62hi25Y5Eif9WmEWagWdkwGXHi2dMtn44bo8,3637
|
|
9
9
|
growthbook/plugins/growthbook_tracking.py,sha256=yN2xOHtRNsJuxkm16wY0YBQFxjEXDKnKcup7C9bQwe4,11351
|
|
10
10
|
growthbook/plugins/request_context.py,sha256=WzoGxalxPfrsN3RzfkvVYaUGat1A3N4AErnaS9IZ48Y,13005
|
|
11
|
-
growthbook-2.1.
|
|
12
|
-
growthbook-2.1.
|
|
13
|
-
growthbook-2.1.
|
|
14
|
-
growthbook-2.1.
|
|
15
|
-
growthbook-2.1.
|
|
11
|
+
growthbook-2.1.3.dist-info/licenses/LICENSE,sha256=D-TcBckB0dTPUlNJ8jBiTIJIj1ekHLB1CY7HJtJKhMY,1069
|
|
12
|
+
growthbook-2.1.3.dist-info/METADATA,sha256=9xHr31IEdPDrsDwkIZV4ybzXAkqoex0QU6EMVz9ZnFY,22726
|
|
13
|
+
growthbook-2.1.3.dist-info/WHEEL,sha256=Mk1ST5gDzEO5il5kYREiBnzzM469m5sI8ESPl7TRhJY,110
|
|
14
|
+
growthbook-2.1.3.dist-info/top_level.txt,sha256=dzfRQFGYejCIUstRSrrRVTMlxf7pBqASTI5S8gGRlXw,11
|
|
15
|
+
growthbook-2.1.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|