posthoganalytics 6.6.1__py3-none-any.whl → 6.7.1__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.
@@ -1376,7 +1376,7 @@ class TestLocalEvaluation(unittest.TestCase):
1376
1376
  "properties": [
1377
1377
  {
1378
1378
  "key": "beta-feature",
1379
- "operator": "exact",
1379
+ "operator": "flag_evaluates_to",
1380
1380
  "value": True,
1381
1381
  "type": "flag",
1382
1382
  "dependency_chain": ["beta-feature"],
@@ -1456,7 +1456,7 @@ class TestLocalEvaluation(unittest.TestCase):
1456
1456
  "properties": [
1457
1457
  {
1458
1458
  "key": "flag-a",
1459
- "operator": "exact",
1459
+ "operator": "flag_evaluates_to",
1460
1460
  "value": True,
1461
1461
  "type": "flag",
1462
1462
  "dependency_chain": ["flag-a"],
@@ -1504,7 +1504,7 @@ class TestLocalEvaluation(unittest.TestCase):
1504
1504
  "properties": [
1505
1505
  {
1506
1506
  "key": "flag-b",
1507
- "operator": "exact",
1507
+ "operator": "flag_evaluates_to",
1508
1508
  "value": True,
1509
1509
  "type": "flag",
1510
1510
  "dependency_chain": [], # Empty chain indicates circular dependency
@@ -1526,7 +1526,7 @@ class TestLocalEvaluation(unittest.TestCase):
1526
1526
  "properties": [
1527
1527
  {
1528
1528
  "key": "flag-a",
1529
- "operator": "exact",
1529
+ "operator": "flag_evaluates_to",
1530
1530
  "value": True,
1531
1531
  "type": "flag",
1532
1532
  "dependency_chain": [], # Empty chain indicates circular dependency
@@ -1566,7 +1566,7 @@ class TestLocalEvaluation(unittest.TestCase):
1566
1566
  "properties": [
1567
1567
  {
1568
1568
  "key": "non-existent-flag",
1569
- "operator": "exact",
1569
+ "operator": "flag_evaluates_to",
1570
1570
  "value": True,
1571
1571
  "type": "flag",
1572
1572
  "dependency_chain": ["non-existent-flag"],
@@ -1629,14 +1629,14 @@ class TestLocalEvaluation(unittest.TestCase):
1629
1629
  "properties": [
1630
1630
  {
1631
1631
  "key": "flag-a",
1632
- "operator": "exact",
1632
+ "operator": "flag_evaluates_to",
1633
1633
  "value": True,
1634
1634
  "type": "flag",
1635
1635
  "dependency_chain": ["flag-a"],
1636
1636
  },
1637
1637
  {
1638
1638
  "key": "flag-b",
1639
- "operator": "exact",
1639
+ "operator": "flag_evaluates_to",
1640
1640
  "value": True,
1641
1641
  "type": "flag",
1642
1642
  "dependency_chain": ["flag-b"],
@@ -1658,7 +1658,7 @@ class TestLocalEvaluation(unittest.TestCase):
1658
1658
  "properties": [
1659
1659
  {
1660
1660
  "key": "flag-c",
1661
- "operator": "exact",
1661
+ "operator": "flag_evaluates_to",
1662
1662
  "value": True,
1663
1663
  "type": "flag",
1664
1664
  "dependency_chain": ["flag-a", "flag-b", "flag-c"],
@@ -1711,7 +1711,7 @@ class TestLocalEvaluation(unittest.TestCase):
1711
1711
  "properties": [
1712
1712
  {
1713
1713
  "key": "base-flag",
1714
- "operator": "exact",
1714
+ "operator": "flag_evaluates_to",
1715
1715
  "value": True,
1716
1716
  "type": "flag",
1717
1717
  "dependency_chain": ["base-flag"],
@@ -1788,10 +1788,10 @@ class TestLocalEvaluation(unittest.TestCase):
1788
1788
  "properties": [
1789
1789
  {
1790
1790
  "key": "base-flag",
1791
- "operator": "exact",
1791
+ "operator": "flag_evaluates_to",
1792
1792
  "value": True,
1793
1793
  "type": "flag",
1794
- # No dependency_chain property - should handle gracefully
1794
+ # No dependency_chain property - should evaluate as inconclusive
1795
1795
  }
1796
1796
  ],
1797
1797
  "rollout_percentage": 100,
@@ -1815,7 +1815,7 @@ class TestLocalEvaluation(unittest.TestCase):
1815
1815
 
1816
1816
  property_with_flag_dep = {
1817
1817
  "key": "some-flag",
1818
- "operator": "exact",
1818
+ "operator": "flag_evaluates_to",
1819
1819
  "value": True,
1820
1820
  "type": "flag",
1821
1821
  "dependency_chain": ["some-flag"],
@@ -1835,6 +1835,516 @@ class TestLocalEvaluation(unittest.TestCase):
1835
1835
  self.assertIn("Cannot evaluate flag dependency", str(cm.exception))
1836
1836
  self.assertIn("some-flag", str(cm.exception))
1837
1837
 
1838
+ @mock.patch("posthog.client.flags")
1839
+ @mock.patch("posthog.client.get")
1840
+ def test_multi_level_multivariate_dependency_chain(self, patch_get, patch_flags):
1841
+ """Test multi-level multivariate dependency chain: dependent-flag -> intermediate-flag -> leaf-flag"""
1842
+ client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY)
1843
+ client.feature_flags = [
1844
+ # Leaf flag: multivariate with "control" and "test" variants using person property overrides
1845
+ {
1846
+ "id": 1,
1847
+ "name": "Leaf Flag",
1848
+ "key": "leaf-flag",
1849
+ "active": True,
1850
+ "rollout_percentage": 100,
1851
+ "filters": {
1852
+ "groups": [
1853
+ {
1854
+ "properties": [
1855
+ {
1856
+ "key": "email",
1857
+ "type": "person",
1858
+ "value": "control@example.com",
1859
+ "operator": "exact",
1860
+ }
1861
+ ],
1862
+ "rollout_percentage": 100,
1863
+ "variant": "control",
1864
+ },
1865
+ {
1866
+ "properties": [
1867
+ {
1868
+ "key": "email",
1869
+ "type": "person",
1870
+ "value": "test@example.com",
1871
+ "operator": "exact",
1872
+ }
1873
+ ],
1874
+ "rollout_percentage": 100,
1875
+ "variant": "test",
1876
+ },
1877
+ {
1878
+ "rollout_percentage": 50,
1879
+ "variant": "control",
1880
+ }, # Default fallback
1881
+ ],
1882
+ "multivariate": {
1883
+ "variants": [
1884
+ {
1885
+ "key": "control",
1886
+ "name": "Control",
1887
+ "rollout_percentage": 50,
1888
+ },
1889
+ {"key": "test", "name": "Test", "rollout_percentage": 50},
1890
+ ]
1891
+ },
1892
+ },
1893
+ },
1894
+ # Intermediate flag: multivariate with "blue" and "green" variants, depends on leaf-flag="control"
1895
+ {
1896
+ "id": 2,
1897
+ "name": "Intermediate Flag",
1898
+ "key": "intermediate-flag",
1899
+ "active": True,
1900
+ "rollout_percentage": 100,
1901
+ "filters": {
1902
+ "groups": [
1903
+ {
1904
+ "properties": [
1905
+ {
1906
+ "key": "leaf-flag",
1907
+ "operator": "flag_evaluates_to",
1908
+ "value": "control",
1909
+ "type": "flag",
1910
+ "dependency_chain": ["leaf-flag"],
1911
+ },
1912
+ {
1913
+ "key": "variant_type",
1914
+ "type": "person",
1915
+ "value": "blue",
1916
+ "operator": "exact",
1917
+ },
1918
+ ],
1919
+ "rollout_percentage": 100,
1920
+ "variant": "blue",
1921
+ },
1922
+ {
1923
+ "properties": [
1924
+ {
1925
+ "key": "leaf-flag",
1926
+ "operator": "flag_evaluates_to",
1927
+ "value": "control",
1928
+ "type": "flag",
1929
+ "dependency_chain": ["leaf-flag"],
1930
+ },
1931
+ {
1932
+ "key": "variant_type",
1933
+ "type": "person",
1934
+ "value": "green",
1935
+ "operator": "exact",
1936
+ },
1937
+ ],
1938
+ "rollout_percentage": 100,
1939
+ "variant": "green",
1940
+ },
1941
+ ],
1942
+ "multivariate": {
1943
+ "variants": [
1944
+ {"key": "blue", "name": "Blue", "rollout_percentage": 50},
1945
+ {"key": "green", "name": "Green", "rollout_percentage": 50},
1946
+ ]
1947
+ },
1948
+ },
1949
+ },
1950
+ # Dependent flag: boolean flag that depends on intermediate-flag="blue"
1951
+ {
1952
+ "id": 3,
1953
+ "name": "Dependent Flag",
1954
+ "key": "dependent-flag",
1955
+ "active": True,
1956
+ "rollout_percentage": 100,
1957
+ "filters": {
1958
+ "groups": [
1959
+ {
1960
+ "properties": [
1961
+ {
1962
+ "key": "intermediate-flag",
1963
+ "operator": "flag_evaluates_to",
1964
+ "value": "blue",
1965
+ "type": "flag",
1966
+ "dependency_chain": [
1967
+ "leaf-flag",
1968
+ "intermediate-flag",
1969
+ ],
1970
+ }
1971
+ ],
1972
+ "rollout_percentage": 100,
1973
+ }
1974
+ ],
1975
+ },
1976
+ },
1977
+ ]
1978
+
1979
+ # Test using person properties and variant overrides to ensure predictable variants
1980
+
1981
+ # Test 1: Make sure the leaf flag evaluates to the variant we expect using email overrides
1982
+ self.assertEqual(
1983
+ "control",
1984
+ client.get_feature_flag(
1985
+ "leaf-flag",
1986
+ "any-user",
1987
+ person_properties={"email": "control@example.com"},
1988
+ ),
1989
+ )
1990
+ self.assertEqual(
1991
+ "test",
1992
+ client.get_feature_flag(
1993
+ "leaf-flag",
1994
+ "any-user",
1995
+ person_properties={"email": "test@example.com"},
1996
+ ),
1997
+ )
1998
+
1999
+ # Test 2: Make sure the intermediate flag evaluates to the expected variants when dependency is satisfied
2000
+ self.assertEqual(
2001
+ "blue",
2002
+ client.get_feature_flag(
2003
+ "intermediate-flag",
2004
+ "any-user",
2005
+ person_properties={
2006
+ "email": "control@example.com",
2007
+ "variant_type": "blue",
2008
+ },
2009
+ ),
2010
+ )
2011
+
2012
+ self.assertEqual(
2013
+ "green",
2014
+ client.get_feature_flag(
2015
+ "intermediate-flag",
2016
+ "any-user",
2017
+ person_properties={
2018
+ "email": "control@example.com",
2019
+ "variant_type": "green",
2020
+ },
2021
+ ),
2022
+ )
2023
+
2024
+ # Test 3: Make sure the intermediate flag evaluates to false when leaf dependency fails
2025
+ self.assertEqual(
2026
+ False,
2027
+ client.get_feature_flag(
2028
+ "intermediate-flag",
2029
+ "any-user",
2030
+ person_properties={
2031
+ "email": "test@example.com", # This makes leaf-flag="test", breaking dependency
2032
+ "variant_type": "blue",
2033
+ },
2034
+ ),
2035
+ )
2036
+
2037
+ # Test 4: When leaf-flag="control", intermediate="blue", dependent should be true
2038
+ self.assertEqual(
2039
+ True,
2040
+ client.get_feature_flag(
2041
+ "dependent-flag",
2042
+ "any-user",
2043
+ person_properties={
2044
+ "email": "control@example.com",
2045
+ "variant_type": "blue",
2046
+ },
2047
+ ),
2048
+ )
2049
+
2050
+ # Test 5: When leaf-flag="control", intermediate="green", dependent should be false
2051
+ self.assertEqual(
2052
+ False,
2053
+ client.get_feature_flag(
2054
+ "dependent-flag",
2055
+ "any-user",
2056
+ person_properties={
2057
+ "email": "control@example.com",
2058
+ "variant_type": "green",
2059
+ },
2060
+ ),
2061
+ )
2062
+
2063
+ # Test 6: When leaf-flag="test", intermediate is False, dependent should be false
2064
+ self.assertEqual(
2065
+ False,
2066
+ client.get_feature_flag(
2067
+ "dependent-flag",
2068
+ "any-user",
2069
+ person_properties={"email": "test@example.com", "variant_type": "blue"},
2070
+ ),
2071
+ )
2072
+
2073
+ def test_matches_dependency_value(self):
2074
+ """Test the matches_dependency_value function logic"""
2075
+ from posthoganalytics.feature_flags import matches_dependency_value
2076
+
2077
+ # String variant matches string exactly (case-sensitive)
2078
+ self.assertTrue(matches_dependency_value("control", "control"))
2079
+ self.assertTrue(matches_dependency_value("Control", "Control"))
2080
+ self.assertFalse(matches_dependency_value("control", "Control"))
2081
+ self.assertFalse(matches_dependency_value("Control", "CONTROL"))
2082
+ self.assertFalse(matches_dependency_value("control", "test"))
2083
+
2084
+ # String variant matches boolean true (any variant is truthy)
2085
+ self.assertTrue(matches_dependency_value(True, "control"))
2086
+ self.assertTrue(matches_dependency_value(True, "test"))
2087
+ self.assertFalse(matches_dependency_value(False, "control"))
2088
+
2089
+ # Boolean matches boolean exactly
2090
+ self.assertTrue(matches_dependency_value(True, True))
2091
+ self.assertTrue(matches_dependency_value(False, False))
2092
+ self.assertFalse(matches_dependency_value(False, True))
2093
+ self.assertFalse(matches_dependency_value(True, False))
2094
+
2095
+ # Empty string doesn't match
2096
+ self.assertFalse(matches_dependency_value(True, ""))
2097
+ self.assertFalse(matches_dependency_value("control", ""))
2098
+
2099
+ # Type mismatches
2100
+ self.assertFalse(matches_dependency_value(123, "control"))
2101
+ self.assertFalse(matches_dependency_value("control", True))
2102
+
2103
+ @mock.patch("posthog.client.flags")
2104
+ @mock.patch("posthog.client.get")
2105
+ def test_production_style_multivariate_dependency_chain(
2106
+ self, patch_get, patch_flags
2107
+ ):
2108
+ """Test production-style multivariate dependency chain: multivariate-root-flag -> multivariate-intermediate-flag -> multivariate-leaf-flag"""
2109
+ client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY)
2110
+ client.feature_flags = [
2111
+ # Leaf flag: multivariate with fruit variants
2112
+ {
2113
+ "id": 451,
2114
+ "name": "Multivariate Leaf Flag (Base)",
2115
+ "key": "multivariate-leaf-flag",
2116
+ "active": True,
2117
+ "rollout_percentage": 100,
2118
+ "filters": {
2119
+ "groups": [
2120
+ {
2121
+ "properties": [
2122
+ {
2123
+ "key": "email",
2124
+ "type": "person",
2125
+ "value": ["pineapple@example.com"],
2126
+ "operator": "exact",
2127
+ }
2128
+ ],
2129
+ "rollout_percentage": 100,
2130
+ "variant": "pineapple",
2131
+ },
2132
+ {
2133
+ "properties": [
2134
+ {
2135
+ "key": "email",
2136
+ "type": "person",
2137
+ "value": ["mango@example.com"],
2138
+ "operator": "exact",
2139
+ }
2140
+ ],
2141
+ "rollout_percentage": 100,
2142
+ "variant": "mango",
2143
+ },
2144
+ {
2145
+ "properties": [
2146
+ {
2147
+ "key": "email",
2148
+ "type": "person",
2149
+ "value": ["papaya@example.com"],
2150
+ "operator": "exact",
2151
+ }
2152
+ ],
2153
+ "rollout_percentage": 100,
2154
+ "variant": "papaya",
2155
+ },
2156
+ {
2157
+ "properties": [
2158
+ {
2159
+ "key": "email",
2160
+ "type": "person",
2161
+ "value": ["kiwi@example.com"],
2162
+ "operator": "exact",
2163
+ }
2164
+ ],
2165
+ "rollout_percentage": 100,
2166
+ "variant": "kiwi",
2167
+ },
2168
+ {
2169
+ "properties": [],
2170
+ "rollout_percentage": 0, # Force default to false for unknown emails
2171
+ },
2172
+ ],
2173
+ "multivariate": {
2174
+ "variants": [
2175
+ {"key": "pineapple", "rollout_percentage": 25},
2176
+ {"key": "mango", "rollout_percentage": 25},
2177
+ {"key": "papaya", "rollout_percentage": 25},
2178
+ {"key": "kiwi", "rollout_percentage": 25},
2179
+ ]
2180
+ },
2181
+ },
2182
+ },
2183
+ # Intermediate flag: multivariate with color variants, depends on fruit
2184
+ {
2185
+ "id": 467,
2186
+ "name": "Multivariate Intermediate Flag (Depends on fruit)",
2187
+ "key": "multivariate-intermediate-flag",
2188
+ "active": True,
2189
+ "rollout_percentage": 100,
2190
+ "filters": {
2191
+ "groups": [
2192
+ {
2193
+ "properties": [
2194
+ {
2195
+ "key": "multivariate-leaf-flag",
2196
+ "type": "flag",
2197
+ "value": "pineapple",
2198
+ "operator": "flag_evaluates_to",
2199
+ "dependency_chain": ["multivariate-leaf-flag"],
2200
+ }
2201
+ ],
2202
+ "rollout_percentage": 100,
2203
+ "variant": "blue",
2204
+ },
2205
+ {
2206
+ "properties": [
2207
+ {
2208
+ "key": "multivariate-leaf-flag",
2209
+ "type": "flag",
2210
+ "value": "mango",
2211
+ "operator": "flag_evaluates_to",
2212
+ "dependency_chain": ["multivariate-leaf-flag"],
2213
+ }
2214
+ ],
2215
+ "rollout_percentage": 100,
2216
+ "variant": "red",
2217
+ },
2218
+ ],
2219
+ "multivariate": {
2220
+ "variants": [
2221
+ {"key": "blue", "rollout_percentage": 100},
2222
+ {"key": "red", "rollout_percentage": 0},
2223
+ {"key": "green", "rollout_percentage": 0},
2224
+ {"key": "black", "rollout_percentage": 0},
2225
+ ]
2226
+ },
2227
+ },
2228
+ },
2229
+ # Root flag: multivariate with show variants, depends on color
2230
+ {
2231
+ "id": 468,
2232
+ "name": "Multivariate Root Flag (Depends on color)",
2233
+ "key": "multivariate-root-flag",
2234
+ "active": True,
2235
+ "rollout_percentage": 100,
2236
+ "filters": {
2237
+ "groups": [
2238
+ {
2239
+ "properties": [
2240
+ {
2241
+ "key": "multivariate-intermediate-flag",
2242
+ "type": "flag",
2243
+ "value": "blue",
2244
+ "operator": "flag_evaluates_to",
2245
+ "dependency_chain": [
2246
+ "multivariate-leaf-flag",
2247
+ "multivariate-intermediate-flag",
2248
+ ],
2249
+ }
2250
+ ],
2251
+ "rollout_percentage": 100,
2252
+ "variant": "breaking-bad",
2253
+ },
2254
+ {
2255
+ "properties": [
2256
+ {
2257
+ "key": "multivariate-intermediate-flag",
2258
+ "type": "flag",
2259
+ "value": "red",
2260
+ "operator": "flag_evaluates_to",
2261
+ "dependency_chain": [
2262
+ "multivariate-leaf-flag",
2263
+ "multivariate-intermediate-flag",
2264
+ ],
2265
+ }
2266
+ ],
2267
+ "rollout_percentage": 100,
2268
+ "variant": "the-wire",
2269
+ },
2270
+ ],
2271
+ "multivariate": {
2272
+ "variants": [
2273
+ {"key": "breaking-bad", "rollout_percentage": 100},
2274
+ {"key": "the-wire", "rollout_percentage": 0},
2275
+ {"key": "game-of-thrones", "rollout_percentage": 0},
2276
+ {"key": "the-expanse", "rollout_percentage": 0},
2277
+ ]
2278
+ },
2279
+ },
2280
+ },
2281
+ ]
2282
+
2283
+ # Test successful pineapple -> blue -> breaking-bad chain
2284
+ leaf_result = client.get_feature_flag(
2285
+ "multivariate-leaf-flag",
2286
+ "test-user",
2287
+ person_properties={"email": "pineapple@example.com"},
2288
+ )
2289
+ intermediate_result = client.get_feature_flag(
2290
+ "multivariate-intermediate-flag",
2291
+ "test-user",
2292
+ person_properties={"email": "pineapple@example.com"},
2293
+ )
2294
+ root_result = client.get_feature_flag(
2295
+ "multivariate-root-flag",
2296
+ "test-user",
2297
+ person_properties={"email": "pineapple@example.com"},
2298
+ )
2299
+
2300
+ self.assertEqual(leaf_result, "pineapple")
2301
+ self.assertEqual(intermediate_result, "blue")
2302
+ self.assertEqual(root_result, "breaking-bad")
2303
+
2304
+ # Test successful mango -> red -> the-wire chain
2305
+ mango_leaf_result = client.get_feature_flag(
2306
+ "multivariate-leaf-flag",
2307
+ "test-user",
2308
+ person_properties={"email": "mango@example.com"},
2309
+ )
2310
+ mango_intermediate_result = client.get_feature_flag(
2311
+ "multivariate-intermediate-flag",
2312
+ "test-user",
2313
+ person_properties={"email": "mango@example.com"},
2314
+ )
2315
+ mango_root_result = client.get_feature_flag(
2316
+ "multivariate-root-flag",
2317
+ "test-user",
2318
+ person_properties={"email": "mango@example.com"},
2319
+ )
2320
+
2321
+ self.assertEqual(mango_leaf_result, "mango")
2322
+ self.assertEqual(mango_intermediate_result, "red")
2323
+ self.assertEqual(mango_root_result, "the-wire")
2324
+
2325
+ # Test broken chain - user without matching email gets default/false results
2326
+ unknown_leaf_result = client.get_feature_flag(
2327
+ "multivariate-leaf-flag",
2328
+ "test-user",
2329
+ person_properties={"email": "unknown@example.com"},
2330
+ )
2331
+ unknown_intermediate_result = client.get_feature_flag(
2332
+ "multivariate-intermediate-flag",
2333
+ "test-user",
2334
+ person_properties={"email": "unknown@example.com"},
2335
+ )
2336
+ unknown_root_result = client.get_feature_flag(
2337
+ "multivariate-root-flag",
2338
+ "test-user",
2339
+ person_properties={"email": "unknown@example.com"},
2340
+ )
2341
+
2342
+ self.assertEqual(
2343
+ unknown_leaf_result, False
2344
+ ) # No matching email -> null variant -> false
2345
+ self.assertEqual(unknown_intermediate_result, False) # Dependency not satisfied
2346
+ self.assertEqual(unknown_root_result, False) # Chain broken
2347
+
1838
2348
  @mock.patch("posthog.client.Poller")
1839
2349
  @mock.patch("posthog.client.get")
1840
2350
  def test_load_feature_flags(self, patch_get, patch_poll):
@@ -1,4 +1,4 @@
1
- VERSION = "6.6.1"
1
+ VERSION = "6.7.1"
2
2
 
3
3
  if __name__ == "__main__":
4
4
  print(VERSION, end="") # noqa: T201
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: posthoganalytics
3
- Version: 6.6.1
3
+ Version: 6.7.1
4
4
  Summary: Integrate PostHog into any python application.
5
5
  Home-page: https://github.com/posthog/posthog-python
6
6
  Author: Posthog