Skip to content

Commit 675dea1

Browse files
authored
feat(flags): implement local evaluation of flag dependency filters (#311)
1 parent 6a3e7ef commit 675dea1

File tree

4 files changed

+696
-48
lines changed

4 files changed

+696
-48
lines changed

example.py

Lines changed: 85 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,11 @@ def load_env_file():
8282
print("1. Identify and capture examples")
8383
print("2. Feature flag local evaluation examples")
8484
print("3. Feature flag payload examples")
85-
print("4. Context management and tagging examples")
86-
print("5. Run all examples")
87-
print("6. Exit")
88-
choice = input("\nEnter your choice (1-6): ").strip()
85+
print("4. Flag dependencies examples")
86+
print("5. Context management and tagging examples")
87+
print("6. Run all examples")
88+
print("7. Exit")
89+
choice = input("\nEnter your choice (1-7): ").strip()
8990

9091
if choice == "1":
9192
print("\n" + "=" * 60)
@@ -214,6 +215,66 @@ def load_env_file():
214215
print(f"Value (variant or enabled): {result.get_value()}")
215216

216217
elif choice == "4":
218+
print("\n" + "=" * 60)
219+
print("FLAG DEPENDENCIES EXAMPLES")
220+
print("=" * 60)
221+
print("🔗 Testing flag dependencies with local evaluation...")
222+
print(
223+
" Flag structure: 'test-flag-dependency' depends on 'beta-feature' being enabled"
224+
)
225+
print("")
226+
print("📋 Required setup (if 'test-flag-dependency' doesn't exist):")
227+
print(" 1. Create feature flag 'beta-feature':")
228+
print(" - Condition: email contains '@example.com'")
229+
print(" - Rollout: 100%")
230+
print(" 2. Create feature flag 'test-flag-dependency':")
231+
print(" - Condition: flag 'beta-feature' is enabled")
232+
print(" - Rollout: 100%")
233+
print("")
234+
235+
posthog.debug = True
236+
237+
# Test @example.com user (should satisfy dependency if flags exist)
238+
result1 = posthog.feature_enabled(
239+
"test-flag-dependency",
240+
"example_user",
241+
person_properties={"email": "[email protected]"},
242+
only_evaluate_locally=True,
243+
)
244+
print(f"✅ @example.com user (test-flag-dependency): {result1}")
245+
246+
# Test non-example.com user (dependency should not be satisfied)
247+
result2 = posthog.feature_enabled(
248+
"test-flag-dependency",
249+
"regular_user",
250+
person_properties={"email": "[email protected]"},
251+
only_evaluate_locally=True,
252+
)
253+
print(f"❌ Regular user (test-flag-dependency): {result2}")
254+
255+
# Test beta-feature directly for comparison
256+
beta1 = posthog.feature_enabled(
257+
"beta-feature",
258+
"example_user",
259+
person_properties={"email": "[email protected]"},
260+
only_evaluate_locally=True,
261+
)
262+
beta2 = posthog.feature_enabled(
263+
"beta-feature",
264+
"regular_user",
265+
person_properties={"email": "[email protected]"},
266+
only_evaluate_locally=True,
267+
)
268+
print(f"📊 Beta feature comparison - @example.com: {beta1}, regular: {beta2}")
269+
270+
print("\n🎯 Results Summary:")
271+
print(
272+
f" - Flag dependencies evaluated locally: {'✅ YES' if result1 != result2 else '❌ NO'}"
273+
)
274+
print(" - Zero API calls needed: ✅ YES (all evaluated locally)")
275+
print(" - Python SDK supports flag dependencies: ✅ YES")
276+
277+
elif choice == "5":
217278
print("\n" + "=" * 60)
218279
print("CONTEXT MANAGEMENT AND TAGGING EXAMPLES")
219280
print("=" * 60)
@@ -274,7 +335,7 @@ def process_payment(payment_id):
274335
process_order("12345")
275336
process_payment("67890")
276337

277-
elif choice == "5":
338+
elif choice == "6":
278339
print("\n🔄 Running all examples...")
279340

280341
# Run example 1
@@ -308,20 +369,37 @@ def process_payment(payment_id):
308369
print(f"Payload: {posthog.get_feature_flag_payload('beta-feature', 'distinct_id')}")
309370

310371
# Run example 4
372+
print(f"\n{'🔸' * 20} FLAG DEPENDENCIES {'🔸' * 20}")
373+
print("🔗 Testing flag dependencies...")
374+
result1 = posthog.feature_enabled(
375+
"test-flag-dependency",
376+
"demo_user",
377+
person_properties={"email": "[email protected]"},
378+
only_evaluate_locally=True,
379+
)
380+
result2 = posthog.feature_enabled(
381+
"test-flag-dependency",
382+
"demo_user2",
383+
person_properties={"email": "[email protected]"},
384+
only_evaluate_locally=True,
385+
)
386+
print(f"✅ @example.com user: {result1}, regular user: {result2}")
387+
388+
# Run example 5
311389
print(f"\n{'🔸' * 20} CONTEXT MANAGEMENT {'🔸' * 20}")
312390
print("🏷️ Testing context management...")
313391
with posthog.new_context():
314392
posthog.tag("demo_run", "all_examples")
315393
posthog.capture("demo_completed")
316394
print("✅ Demo completed with context tags")
317395

318-
elif choice == "6":
396+
elif choice == "7":
319397
print("👋 Goodbye!")
320398
posthog.shutdown()
321399
exit()
322400

323401
else:
324-
print("❌ Invalid choice. Please run again and select 1-6.")
402+
print("❌ Invalid choice. Please run again and select 1-7.")
325403
posthog.shutdown()
326404
exit()
327405

posthog/client.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,6 +1204,9 @@ def _compute_flag_locally(
12041204
person_properties = person_properties or {}
12051205
group_properties = group_properties or {}
12061206

1207+
# Create evaluation cache for flag dependencies
1208+
evaluation_cache: dict[str, Optional[FlagValue]] = {}
1209+
12071210
if feature_flag.get("ensure_experience_continuity", False):
12081211
raise InconclusiveMatchError("Flag has experience continuity enabled")
12091212

@@ -1237,11 +1240,20 @@ def _compute_flag_locally(
12371240

12381241
focused_group_properties = group_properties[group_name]
12391242
return match_feature_flag_properties(
1240-
feature_flag, groups[group_name], focused_group_properties
1243+
feature_flag,
1244+
groups[group_name],
1245+
focused_group_properties,
1246+
self.feature_flags_by_key,
1247+
evaluation_cache,
12411248
)
12421249
else:
12431250
return match_feature_flag_properties(
1244-
feature_flag, distinct_id, person_properties, self.cohorts
1251+
feature_flag,
1252+
distinct_id,
1253+
person_properties,
1254+
self.cohorts,
1255+
self.feature_flags_by_key,
1256+
evaluation_cache,
12451257
)
12461258

12471259
def feature_enabled(

0 commit comments

Comments
 (0)