posthoganalytics 6.6.0__tar.gz → 6.7.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.
- {posthoganalytics-6.6.0/posthoganalytics.egg-info → posthoganalytics-6.7.0}/PKG-INFO +1 -1
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/ai/anthropic/anthropic.py +1 -1
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/ai/openai/openai_async.py +1 -1
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/client.py +25 -13
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/feature_flags.py +230 -20
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/test/test_feature_flags.py +939 -20
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/version.py +1 -1
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0/posthoganalytics.egg-info}/PKG-INFO +1 -1
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/LICENSE +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/MANIFEST.in +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/README.md +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/__init__.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/ai/__init__.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/ai/anthropic/__init__.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/ai/anthropic/anthropic_async.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/ai/anthropic/anthropic_providers.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/ai/gemini/__init__.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/ai/gemini/gemini.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/ai/langchain/__init__.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/ai/langchain/callbacks.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/ai/openai/__init__.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/ai/openai/openai.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/ai/openai/openai_providers.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/ai/utils.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/args.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/consumer.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/contexts.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/exception_capture.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/exception_utils.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/integrations/__init__.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/integrations/django.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/poller.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/py.typed +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/request.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/test/__init__.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/test/test_before_send.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/test/test_client.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/test/test_consumer.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/test/test_contexts.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/test/test_exception_capture.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/test/test_feature_flag.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/test/test_feature_flag_result.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/test/test_module.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/test/test_request.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/test/test_size_limited_dict.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/test/test_types.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/test/test_utils.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/types.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics/utils.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics.egg-info/SOURCES.txt +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics.egg-info/dependency_links.txt +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics.egg-info/requires.txt +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/posthoganalytics.egg-info/top_level.txt +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/pyproject.toml +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/setup.cfg +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/setup.py +0 -0
- {posthoganalytics-6.6.0 → posthoganalytics-6.7.0}/setup_analytics.py +0 -0
|
@@ -329,7 +329,7 @@ class Client(object):
|
|
|
329
329
|
only these flags will be evaluated, improving performance.
|
|
330
330
|
|
|
331
331
|
Category:
|
|
332
|
-
Feature
|
|
332
|
+
Feature flags
|
|
333
333
|
"""
|
|
334
334
|
resp_data = self.get_flags_decision(
|
|
335
335
|
distinct_id,
|
|
@@ -368,7 +368,7 @@ class Client(object):
|
|
|
368
368
|
```
|
|
369
369
|
|
|
370
370
|
Category:
|
|
371
|
-
Feature
|
|
371
|
+
Feature flags
|
|
372
372
|
"""
|
|
373
373
|
resp_data = self.get_flags_decision(
|
|
374
374
|
distinct_id,
|
|
@@ -407,7 +407,7 @@ class Client(object):
|
|
|
407
407
|
```
|
|
408
408
|
|
|
409
409
|
Category:
|
|
410
|
-
Feature
|
|
410
|
+
Feature flags
|
|
411
411
|
"""
|
|
412
412
|
resp = self.get_flags_decision(
|
|
413
413
|
distinct_id,
|
|
@@ -446,7 +446,7 @@ class Client(object):
|
|
|
446
446
|
```
|
|
447
447
|
|
|
448
448
|
Category:
|
|
449
|
-
Feature
|
|
449
|
+
Feature flags
|
|
450
450
|
"""
|
|
451
451
|
groups = groups or {}
|
|
452
452
|
person_properties = person_properties or {}
|
|
@@ -1169,7 +1169,7 @@ class Client(object):
|
|
|
1169
1169
|
```
|
|
1170
1170
|
|
|
1171
1171
|
Category:
|
|
1172
|
-
Feature
|
|
1172
|
+
Feature flags
|
|
1173
1173
|
"""
|
|
1174
1174
|
if not self.personal_api_key:
|
|
1175
1175
|
self.log.warning(
|
|
@@ -1204,6 +1204,9 @@ class Client(object):
|
|
|
1204
1204
|
person_properties = person_properties or {}
|
|
1205
1205
|
group_properties = group_properties or {}
|
|
1206
1206
|
|
|
1207
|
+
# Create evaluation cache for flag dependencies
|
|
1208
|
+
evaluation_cache: dict[str, Optional[FlagValue]] = {}
|
|
1209
|
+
|
|
1207
1210
|
if feature_flag.get("ensure_experience_continuity", False):
|
|
1208
1211
|
raise InconclusiveMatchError("Flag has experience continuity enabled")
|
|
1209
1212
|
|
|
@@ -1237,11 +1240,20 @@ class Client(object):
|
|
|
1237
1240
|
|
|
1238
1241
|
focused_group_properties = group_properties[group_name]
|
|
1239
1242
|
return match_feature_flag_properties(
|
|
1240
|
-
feature_flag,
|
|
1243
|
+
feature_flag,
|
|
1244
|
+
groups[group_name],
|
|
1245
|
+
focused_group_properties,
|
|
1246
|
+
self.feature_flags_by_key,
|
|
1247
|
+
evaluation_cache,
|
|
1241
1248
|
)
|
|
1242
1249
|
else:
|
|
1243
1250
|
return match_feature_flag_properties(
|
|
1244
|
-
feature_flag,
|
|
1251
|
+
feature_flag,
|
|
1252
|
+
distinct_id,
|
|
1253
|
+
person_properties,
|
|
1254
|
+
self.cohorts,
|
|
1255
|
+
self.feature_flags_by_key,
|
|
1256
|
+
evaluation_cache,
|
|
1245
1257
|
)
|
|
1246
1258
|
|
|
1247
1259
|
def feature_enabled(
|
|
@@ -1279,7 +1291,7 @@ class Client(object):
|
|
|
1279
1291
|
```
|
|
1280
1292
|
|
|
1281
1293
|
Category:
|
|
1282
|
-
Feature
|
|
1294
|
+
Feature flags
|
|
1283
1295
|
"""
|
|
1284
1296
|
response = self.get_feature_flag(
|
|
1285
1297
|
key,
|
|
@@ -1487,7 +1499,7 @@ class Client(object):
|
|
|
1487
1499
|
```
|
|
1488
1500
|
|
|
1489
1501
|
Category:
|
|
1490
|
-
Feature
|
|
1502
|
+
Feature flags
|
|
1491
1503
|
"""
|
|
1492
1504
|
feature_flag_result = self.get_feature_flag_result(
|
|
1493
1505
|
key,
|
|
@@ -1577,7 +1589,7 @@ class Client(object):
|
|
|
1577
1589
|
```
|
|
1578
1590
|
|
|
1579
1591
|
Category:
|
|
1580
|
-
Feature
|
|
1592
|
+
Feature flags
|
|
1581
1593
|
"""
|
|
1582
1594
|
feature_flag_result = self._get_feature_flag_result(
|
|
1583
1595
|
key,
|
|
@@ -1747,7 +1759,7 @@ class Client(object):
|
|
|
1747
1759
|
```
|
|
1748
1760
|
|
|
1749
1761
|
Category:
|
|
1750
|
-
Feature
|
|
1762
|
+
Feature flags
|
|
1751
1763
|
"""
|
|
1752
1764
|
response = self.get_all_flags_and_payloads(
|
|
1753
1765
|
distinct_id,
|
|
@@ -1791,7 +1803,7 @@ class Client(object):
|
|
|
1791
1803
|
```
|
|
1792
1804
|
|
|
1793
1805
|
Category:
|
|
1794
|
-
Feature
|
|
1806
|
+
Feature flags
|
|
1795
1807
|
"""
|
|
1796
1808
|
if self.disabled:
|
|
1797
1809
|
return {"featureFlags": None, "featureFlagPayloads": None}
|
|
@@ -1997,7 +2009,7 @@ class Client(object):
|
|
|
1997
2009
|
for group_name in groups:
|
|
1998
2010
|
all_group_properties[group_name] = {
|
|
1999
2011
|
"$group_key": groups[group_name],
|
|
2000
|
-
**(group_properties.get(group_name) or {}),
|
|
2012
|
+
**((group_properties or {}).get(group_name) or {}),
|
|
2001
2013
|
}
|
|
2002
2014
|
|
|
2003
2015
|
return all_person_properties, all_group_properties
|
|
@@ -55,8 +55,161 @@ def variant_lookup_table(feature_flag):
|
|
|
55
55
|
return lookup_table
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
def evaluate_flag_dependency(
|
|
59
|
+
property, flags_by_key, evaluation_cache, distinct_id, properties, cohort_properties
|
|
60
|
+
):
|
|
61
|
+
"""
|
|
62
|
+
Evaluate a flag dependency property according to the dependency chain algorithm.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
property: Flag property with type="flag" and dependency_chain
|
|
66
|
+
flags_by_key: Dictionary of all flags by their key
|
|
67
|
+
evaluation_cache: Cache for storing evaluation results
|
|
68
|
+
distinct_id: The distinct ID being evaluated
|
|
69
|
+
properties: Person properties for evaluation
|
|
70
|
+
cohort_properties: Cohort properties for evaluation
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
bool: True if all dependencies in the chain evaluate to True, False otherwise
|
|
74
|
+
"""
|
|
75
|
+
if flags_by_key is None or evaluation_cache is None:
|
|
76
|
+
# Cannot evaluate flag dependencies without required context
|
|
77
|
+
raise InconclusiveMatchError(
|
|
78
|
+
f"Cannot evaluate flag dependency on '{property.get('key', 'unknown')}' without flags_by_key and evaluation_cache"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Check if dependency_chain is present - it should always be provided for flag dependencies
|
|
82
|
+
if "dependency_chain" not in property:
|
|
83
|
+
# Missing dependency_chain indicates malformed server data
|
|
84
|
+
raise InconclusiveMatchError(
|
|
85
|
+
f"Flag dependency property for '{property.get('key', 'unknown')}' is missing required 'dependency_chain' field"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
dependency_chain = property["dependency_chain"]
|
|
89
|
+
|
|
90
|
+
# Handle circular dependency (empty chain means circular)
|
|
91
|
+
if len(dependency_chain) == 0:
|
|
92
|
+
log.debug(f"Circular dependency detected for flag: {property.get('key')}")
|
|
93
|
+
raise InconclusiveMatchError(
|
|
94
|
+
f"Circular dependency detected for flag '{property.get('key', 'unknown')}'"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Evaluate all dependencies in the chain order
|
|
98
|
+
for dep_flag_key in dependency_chain:
|
|
99
|
+
if dep_flag_key not in evaluation_cache:
|
|
100
|
+
# Need to evaluate this dependency first
|
|
101
|
+
dep_flag = flags_by_key.get(dep_flag_key)
|
|
102
|
+
if not dep_flag:
|
|
103
|
+
# Missing flag dependency - cannot evaluate locally
|
|
104
|
+
evaluation_cache[dep_flag_key] = None
|
|
105
|
+
raise InconclusiveMatchError(
|
|
106
|
+
f"Cannot evaluate flag dependency '{dep_flag_key}' - flag not found in local flags"
|
|
107
|
+
)
|
|
108
|
+
else:
|
|
109
|
+
# Check if the flag is active (same check as in client._compute_flag_locally)
|
|
110
|
+
if not dep_flag.get("active"):
|
|
111
|
+
evaluation_cache[dep_flag_key] = False
|
|
112
|
+
else:
|
|
113
|
+
# Recursively evaluate the dependency
|
|
114
|
+
try:
|
|
115
|
+
dep_result = match_feature_flag_properties(
|
|
116
|
+
dep_flag,
|
|
117
|
+
distinct_id,
|
|
118
|
+
properties,
|
|
119
|
+
cohort_properties,
|
|
120
|
+
flags_by_key,
|
|
121
|
+
evaluation_cache,
|
|
122
|
+
)
|
|
123
|
+
evaluation_cache[dep_flag_key] = dep_result
|
|
124
|
+
except InconclusiveMatchError as e:
|
|
125
|
+
# If we can't evaluate a dependency, store None and propagate the error
|
|
126
|
+
evaluation_cache[dep_flag_key] = None
|
|
127
|
+
raise InconclusiveMatchError(
|
|
128
|
+
f"Cannot evaluate flag dependency '{dep_flag_key}': {e}"
|
|
129
|
+
) from e
|
|
130
|
+
|
|
131
|
+
# Check the cached result
|
|
132
|
+
cached_result = evaluation_cache[dep_flag_key]
|
|
133
|
+
if cached_result is None:
|
|
134
|
+
# Previously inconclusive - raise error again
|
|
135
|
+
raise InconclusiveMatchError(
|
|
136
|
+
f"Flag dependency '{dep_flag_key}' was previously inconclusive"
|
|
137
|
+
)
|
|
138
|
+
elif not cached_result:
|
|
139
|
+
# Definitive False result - dependency failed
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
# All dependencies in the chain have been evaluated successfully
|
|
143
|
+
# Now check if the final flag value matches the expected value in the property
|
|
144
|
+
flag_key = property.get("key")
|
|
145
|
+
expected_value = property.get("value")
|
|
146
|
+
operator = property.get("operator", "exact")
|
|
147
|
+
|
|
148
|
+
if flag_key and expected_value is not None:
|
|
149
|
+
# Get the actual value of the flag we're checking
|
|
150
|
+
actual_value = evaluation_cache.get(flag_key)
|
|
151
|
+
|
|
152
|
+
if actual_value is None:
|
|
153
|
+
# Flag wasn't evaluated - this shouldn't happen if dependency chain is correct
|
|
154
|
+
raise InconclusiveMatchError(
|
|
155
|
+
f"Flag '{flag_key}' was not evaluated despite being in dependency chain"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# For flag dependencies, we need to compare the actual flag result with expected value
|
|
159
|
+
# using the flag_evaluates_to operator logic
|
|
160
|
+
if operator == "flag_evaluates_to":
|
|
161
|
+
return matches_dependency_value(expected_value, actual_value)
|
|
162
|
+
else:
|
|
163
|
+
# This should never happen, but just to be defensive.
|
|
164
|
+
raise InconclusiveMatchError(
|
|
165
|
+
f"Flag dependency property for '{property.get('key', 'unknown')}' has invalid operator '{operator}'"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# If no value check needed, return True (all dependencies passed)
|
|
169
|
+
return True
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def matches_dependency_value(expected_value, actual_value):
|
|
173
|
+
"""
|
|
174
|
+
Check if the actual flag value matches the expected dependency value.
|
|
175
|
+
|
|
176
|
+
This follows the same logic as the C# MatchesDependencyValue function:
|
|
177
|
+
- String variant case: check for exact match or boolean true
|
|
178
|
+
- Boolean case: must match expected boolean value
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
expected_value: The expected value from the property
|
|
182
|
+
actual_value: The actual value returned by the flag evaluation
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
bool: True if the values match according to flag dependency rules
|
|
186
|
+
"""
|
|
187
|
+
# String variant case - check for exact match or boolean true
|
|
188
|
+
if isinstance(actual_value, str) and len(actual_value) > 0:
|
|
189
|
+
if isinstance(expected_value, bool):
|
|
190
|
+
# Any variant matches boolean true
|
|
191
|
+
return expected_value
|
|
192
|
+
elif isinstance(expected_value, str):
|
|
193
|
+
# variants are case-sensitive, hence our comparison is too
|
|
194
|
+
return actual_value == expected_value
|
|
195
|
+
else:
|
|
196
|
+
return False
|
|
197
|
+
|
|
198
|
+
# Boolean case - must match expected boolean value
|
|
199
|
+
elif isinstance(actual_value, bool) and isinstance(expected_value, bool):
|
|
200
|
+
return actual_value == expected_value
|
|
201
|
+
|
|
202
|
+
# Default case
|
|
203
|
+
return False
|
|
204
|
+
|
|
205
|
+
|
|
58
206
|
def match_feature_flag_properties(
|
|
59
|
-
flag,
|
|
207
|
+
flag,
|
|
208
|
+
distinct_id,
|
|
209
|
+
properties,
|
|
210
|
+
cohort_properties=None,
|
|
211
|
+
flags_by_key=None,
|
|
212
|
+
evaluation_cache=None,
|
|
60
213
|
) -> FlagValue:
|
|
61
214
|
flag_conditions = (flag.get("filters") or {}).get("groups") or []
|
|
62
215
|
is_inconclusive = False
|
|
@@ -79,7 +232,13 @@ def match_feature_flag_properties(
|
|
|
79
232
|
# if any one condition resolves to True, we can shortcircuit and return
|
|
80
233
|
# the matching variant
|
|
81
234
|
if is_condition_match(
|
|
82
|
-
flag,
|
|
235
|
+
flag,
|
|
236
|
+
distinct_id,
|
|
237
|
+
condition,
|
|
238
|
+
properties,
|
|
239
|
+
cohort_properties,
|
|
240
|
+
flags_by_key,
|
|
241
|
+
evaluation_cache,
|
|
83
242
|
):
|
|
84
243
|
variant_override = condition.get("variant")
|
|
85
244
|
if variant_override and variant_override in valid_variant_keys:
|
|
@@ -101,22 +260,36 @@ def match_feature_flag_properties(
|
|
|
101
260
|
|
|
102
261
|
|
|
103
262
|
def is_condition_match(
|
|
104
|
-
feature_flag,
|
|
263
|
+
feature_flag,
|
|
264
|
+
distinct_id,
|
|
265
|
+
condition,
|
|
266
|
+
properties,
|
|
267
|
+
cohort_properties,
|
|
268
|
+
flags_by_key=None,
|
|
269
|
+
evaluation_cache=None,
|
|
105
270
|
) -> bool:
|
|
106
271
|
rollout_percentage = condition.get("rollout_percentage")
|
|
107
272
|
if len(condition.get("properties") or []) > 0:
|
|
108
273
|
for prop in condition.get("properties"):
|
|
109
274
|
property_type = prop.get("type")
|
|
110
275
|
if property_type == "cohort":
|
|
111
|
-
matches = match_cohort(
|
|
276
|
+
matches = match_cohort(
|
|
277
|
+
prop,
|
|
278
|
+
properties,
|
|
279
|
+
cohort_properties,
|
|
280
|
+
flags_by_key,
|
|
281
|
+
evaluation_cache,
|
|
282
|
+
distinct_id,
|
|
283
|
+
)
|
|
112
284
|
elif property_type == "flag":
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
285
|
+
matches = evaluate_flag_dependency(
|
|
286
|
+
prop,
|
|
287
|
+
flags_by_key,
|
|
288
|
+
evaluation_cache,
|
|
289
|
+
distinct_id,
|
|
290
|
+
properties,
|
|
291
|
+
cohort_properties,
|
|
118
292
|
)
|
|
119
|
-
continue
|
|
120
293
|
else:
|
|
121
294
|
matches = match_property(prop, properties)
|
|
122
295
|
if not matches:
|
|
@@ -264,7 +437,14 @@ def match_property(property, property_values) -> bool:
|
|
|
264
437
|
raise InconclusiveMatchError(f"Unknown operator {operator}")
|
|
265
438
|
|
|
266
439
|
|
|
267
|
-
def match_cohort(
|
|
440
|
+
def match_cohort(
|
|
441
|
+
property,
|
|
442
|
+
property_values,
|
|
443
|
+
cohort_properties,
|
|
444
|
+
flags_by_key=None,
|
|
445
|
+
evaluation_cache=None,
|
|
446
|
+
distinct_id=None,
|
|
447
|
+
) -> bool:
|
|
268
448
|
# Cohort properties are in the form of property groups like this:
|
|
269
449
|
# {
|
|
270
450
|
# "cohort_id": {
|
|
@@ -281,10 +461,24 @@ def match_cohort(property, property_values, cohort_properties) -> bool:
|
|
|
281
461
|
)
|
|
282
462
|
|
|
283
463
|
property_group = cohort_properties[cohort_id]
|
|
284
|
-
return match_property_group(
|
|
464
|
+
return match_property_group(
|
|
465
|
+
property_group,
|
|
466
|
+
property_values,
|
|
467
|
+
cohort_properties,
|
|
468
|
+
flags_by_key,
|
|
469
|
+
evaluation_cache,
|
|
470
|
+
distinct_id,
|
|
471
|
+
)
|
|
285
472
|
|
|
286
473
|
|
|
287
|
-
def match_property_group(
|
|
474
|
+
def match_property_group(
|
|
475
|
+
property_group,
|
|
476
|
+
property_values,
|
|
477
|
+
cohort_properties,
|
|
478
|
+
flags_by_key=None,
|
|
479
|
+
evaluation_cache=None,
|
|
480
|
+
distinct_id=None,
|
|
481
|
+
) -> bool:
|
|
288
482
|
if not property_group:
|
|
289
483
|
return True
|
|
290
484
|
|
|
@@ -301,7 +495,14 @@ def match_property_group(property_group, property_values, cohort_properties) ->
|
|
|
301
495
|
# a nested property group
|
|
302
496
|
for prop in properties:
|
|
303
497
|
try:
|
|
304
|
-
matches = match_property_group(
|
|
498
|
+
matches = match_property_group(
|
|
499
|
+
prop,
|
|
500
|
+
property_values,
|
|
501
|
+
cohort_properties,
|
|
502
|
+
flags_by_key,
|
|
503
|
+
evaluation_cache,
|
|
504
|
+
distinct_id,
|
|
505
|
+
)
|
|
305
506
|
if property_group_type == "AND":
|
|
306
507
|
if not matches:
|
|
307
508
|
return False
|
|
@@ -324,14 +525,23 @@ def match_property_group(property_group, property_values, cohort_properties) ->
|
|
|
324
525
|
for prop in properties:
|
|
325
526
|
try:
|
|
326
527
|
if prop.get("type") == "cohort":
|
|
327
|
-
matches = match_cohort(
|
|
528
|
+
matches = match_cohort(
|
|
529
|
+
prop,
|
|
530
|
+
property_values,
|
|
531
|
+
cohort_properties,
|
|
532
|
+
flags_by_key,
|
|
533
|
+
evaluation_cache,
|
|
534
|
+
distinct_id,
|
|
535
|
+
)
|
|
328
536
|
elif prop.get("type") == "flag":
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
537
|
+
matches = evaluate_flag_dependency(
|
|
538
|
+
prop,
|
|
539
|
+
flags_by_key,
|
|
540
|
+
evaluation_cache,
|
|
541
|
+
distinct_id,
|
|
542
|
+
property_values,
|
|
543
|
+
cohort_properties,
|
|
333
544
|
)
|
|
334
|
-
continue
|
|
335
545
|
else:
|
|
336
546
|
matches = match_property(prop, property_values)
|
|
337
547
|
|