posthog 7.0.1__py3-none-any.whl → 7.4.0__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.
@@ -11,7 +11,7 @@ from posthog.feature_flags import (
11
11
  match_property,
12
12
  relative_date_parse_for_feature_flag_matching,
13
13
  )
14
- from posthog.request import APIError
14
+ from posthog.request import APIError, GetResponse
15
15
  from posthog.test.test_utils import FAKE_TEST_API_KEY
16
16
 
17
17
 
@@ -2348,23 +2348,27 @@ class TestLocalEvaluation(unittest.TestCase):
2348
2348
  @mock.patch("posthog.client.Poller")
2349
2349
  @mock.patch("posthog.client.get")
2350
2350
  def test_load_feature_flags(self, patch_get, patch_poll):
2351
- patch_get.return_value = {
2352
- "flags": [
2353
- {
2354
- "id": 1,
2355
- "name": "Beta Feature",
2356
- "key": "beta-feature",
2357
- "active": True,
2358
- },
2359
- {
2360
- "id": 2,
2361
- "name": "Alpha Feature",
2362
- "key": "alpha-feature",
2363
- "active": False,
2364
- },
2365
- ],
2366
- "group_type_mapping": {"0": "company"},
2367
- }
2351
+ patch_get.return_value = GetResponse(
2352
+ data={
2353
+ "flags": [
2354
+ {
2355
+ "id": 1,
2356
+ "name": "Beta Feature",
2357
+ "key": "beta-feature",
2358
+ "active": True,
2359
+ },
2360
+ {
2361
+ "id": 2,
2362
+ "name": "Alpha Feature",
2363
+ "key": "alpha-feature",
2364
+ "active": False,
2365
+ },
2366
+ ],
2367
+ "group_type_mapping": {"0": "company"},
2368
+ "cohorts": {},
2369
+ },
2370
+ etag='"abc123"',
2371
+ )
2368
2372
  client = Client(FAKE_TEST_API_KEY, personal_api_key="test")
2369
2373
  with freeze_time("2020-01-01T12:01:00.0000Z"):
2370
2374
  client.load_feature_flags()
@@ -2375,6 +2379,139 @@ class TestLocalEvaluation(unittest.TestCase):
2375
2379
  client._last_feature_flag_poll.isoformat(), "2020-01-01T12:01:00+00:00"
2376
2380
  )
2377
2381
  self.assertEqual(patch_poll.call_count, 1)
2382
+ # Verify ETag is stored
2383
+ self.assertEqual(client._flags_etag, '"abc123"')
2384
+
2385
+ @mock.patch("posthog.client.Poller")
2386
+ @mock.patch("posthog.client.get")
2387
+ def test_load_feature_flags_sends_etag_on_subsequent_requests(
2388
+ self, patch_get, patch_poll
2389
+ ):
2390
+ """Test that the ETag is sent in If-None-Match header on subsequent requests"""
2391
+ patch_get.return_value = GetResponse(
2392
+ data={
2393
+ "flags": [{"id": 1, "key": "beta-feature", "active": True}],
2394
+ "group_type_mapping": {},
2395
+ "cohorts": {},
2396
+ },
2397
+ etag='"initial-etag"',
2398
+ )
2399
+ client = Client(FAKE_TEST_API_KEY, personal_api_key="test")
2400
+ client.load_feature_flags()
2401
+
2402
+ # First call should have no etag
2403
+ first_call_kwargs = patch_get.call_args_list[0][1]
2404
+ self.assertIsNone(first_call_kwargs.get("etag"))
2405
+
2406
+ # Simulate second call
2407
+ client._load_feature_flags()
2408
+
2409
+ # Second call should have the etag
2410
+ second_call_kwargs = patch_get.call_args_list[1][1]
2411
+ self.assertEqual(second_call_kwargs.get("etag"), '"initial-etag"')
2412
+
2413
+ @mock.patch("posthog.client.Poller")
2414
+ @mock.patch("posthog.client.get")
2415
+ def test_load_feature_flags_304_not_modified(self, patch_get, patch_poll):
2416
+ """Test that 304 Not Modified responses skip flag processing"""
2417
+ # First response with flags
2418
+ initial_response = GetResponse(
2419
+ data={
2420
+ "flags": [{"id": 1, "key": "beta-feature", "active": True}],
2421
+ "group_type_mapping": {"0": "company"},
2422
+ "cohorts": {},
2423
+ },
2424
+ etag='"test-etag"',
2425
+ )
2426
+ # Second response is 304 Not Modified
2427
+ not_modified_response = GetResponse(
2428
+ data=None,
2429
+ etag='"test-etag"',
2430
+ not_modified=True,
2431
+ )
2432
+ patch_get.side_effect = [initial_response, not_modified_response]
2433
+
2434
+ client = Client(FAKE_TEST_API_KEY, personal_api_key="test")
2435
+ client.load_feature_flags()
2436
+
2437
+ # Verify initial flags are loaded
2438
+ self.assertEqual(len(client.feature_flags), 1)
2439
+ self.assertEqual(client.feature_flags[0]["key"], "beta-feature")
2440
+ self.assertEqual(client.group_type_mapping, {"0": "company"})
2441
+
2442
+ # Second call with 304
2443
+ client._load_feature_flags()
2444
+
2445
+ # Flags should still be the same (not cleared)
2446
+ self.assertEqual(len(client.feature_flags), 1)
2447
+ self.assertEqual(client.feature_flags[0]["key"], "beta-feature")
2448
+ self.assertEqual(client.group_type_mapping, {"0": "company"})
2449
+
2450
+ @mock.patch("posthog.client.Poller")
2451
+ @mock.patch("posthog.client.get")
2452
+ def test_load_feature_flags_etag_updated_on_new_response(
2453
+ self, patch_get, patch_poll
2454
+ ):
2455
+ """Test that ETag is updated when flags change"""
2456
+ patch_get.side_effect = [
2457
+ GetResponse(
2458
+ data={
2459
+ "flags": [{"id": 1, "key": "flag-v1", "active": True}],
2460
+ "group_type_mapping": {},
2461
+ "cohorts": {},
2462
+ },
2463
+ etag='"etag-v1"',
2464
+ ),
2465
+ GetResponse(
2466
+ data={
2467
+ "flags": [{"id": 1, "key": "flag-v2", "active": True}],
2468
+ "group_type_mapping": {},
2469
+ "cohorts": {},
2470
+ },
2471
+ etag='"etag-v2"',
2472
+ ),
2473
+ ]
2474
+
2475
+ client = Client(FAKE_TEST_API_KEY, personal_api_key="test")
2476
+ client.load_feature_flags()
2477
+ self.assertEqual(client._flags_etag, '"etag-v1"')
2478
+
2479
+ client._load_feature_flags()
2480
+ self.assertEqual(client._flags_etag, '"etag-v2"')
2481
+ self.assertEqual(client.feature_flags[0]["key"], "flag-v2")
2482
+
2483
+ @mock.patch("posthog.client.Poller")
2484
+ @mock.patch("posthog.client.get")
2485
+ def test_load_feature_flags_clears_etag_when_server_stops_sending(
2486
+ self, patch_get, patch_poll
2487
+ ):
2488
+ """Test that ETag is cleared when server stops sending it"""
2489
+ patch_get.side_effect = [
2490
+ GetResponse(
2491
+ data={
2492
+ "flags": [{"id": 1, "key": "flag-v1", "active": True}],
2493
+ "group_type_mapping": {},
2494
+ "cohorts": {},
2495
+ },
2496
+ etag='"etag-v1"',
2497
+ ),
2498
+ GetResponse(
2499
+ data={
2500
+ "flags": [{"id": 1, "key": "flag-v2", "active": True}],
2501
+ "group_type_mapping": {},
2502
+ "cohorts": {},
2503
+ },
2504
+ etag=None, # Server stopped sending ETag
2505
+ ),
2506
+ ]
2507
+
2508
+ client = Client(FAKE_TEST_API_KEY, personal_api_key="test")
2509
+ client.load_feature_flags()
2510
+ self.assertEqual(client._flags_etag, '"etag-v1"')
2511
+
2512
+ client._load_feature_flags()
2513
+ self.assertIsNone(client._flags_etag)
2514
+ self.assertEqual(client.feature_flags[0]["key"], "flag-v2")
2378
2515
 
2379
2516
  def test_load_feature_flags_wrong_key(self):
2380
2517
  client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY)
@@ -3859,6 +3996,7 @@ class TestCaptureCalls(unittest.TestCase):
3859
3996
  },
3860
3997
  },
3861
3998
  "requestId": "18043bf7-9cf6-44cd-b959-9662ee20d371",
3999
+ "evaluatedAt": 1234567890,
3862
4000
  }
3863
4001
  client = Client(FAKE_TEST_API_KEY)
3864
4002
 
@@ -3878,6 +4016,7 @@ class TestCaptureCalls(unittest.TestCase):
3878
4016
  "$feature_flag_id": 23,
3879
4017
  "$feature_flag_version": 42,
3880
4018
  "$feature_flag_request_id": "18043bf7-9cf6-44cd-b959-9662ee20d371",
4019
+ "$feature_flag_evaluated_at": 1234567890,
3881
4020
  },
3882
4021
  groups={},
3883
4022
  disable_geoip=None,