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.
- posthoganalytics/ai/anthropic/anthropic.py +3 -2
- posthoganalytics/ai/anthropic/anthropic_async.py +2 -1
- posthoganalytics/ai/gemini/gemini.py +2 -1
- posthoganalytics/ai/langchain/callbacks.py +3 -2
- posthoganalytics/ai/openai/openai.py +10 -3
- posthoganalytics/ai/openai/openai_async.py +11 -4
- posthoganalytics/ai/sanitization.py +226 -0
- posthoganalytics/ai/utils.py +27 -2
- posthoganalytics/client.py +10 -10
- posthoganalytics/feature_flags.py +61 -0
- posthoganalytics/test/test_feature_flags.py +522 -12
- posthoganalytics/version.py +1 -1
- {posthoganalytics-6.6.1.dist-info → posthoganalytics-6.7.1.dist-info}/METADATA +1 -1
- {posthoganalytics-6.6.1.dist-info → posthoganalytics-6.7.1.dist-info}/RECORD +17 -16
- {posthoganalytics-6.6.1.dist-info → posthoganalytics-6.7.1.dist-info}/WHEEL +0 -0
- {posthoganalytics-6.6.1.dist-info → posthoganalytics-6.7.1.dist-info}/licenses/LICENSE +0 -0
- {posthoganalytics-6.6.1.dist-info → posthoganalytics-6.7.1.dist-info}/top_level.txt +0 -0
|
@@ -1376,7 +1376,7 @@ class TestLocalEvaluation(unittest.TestCase):
|
|
|
1376
1376
|
"properties": [
|
|
1377
1377
|
{
|
|
1378
1378
|
"key": "beta-feature",
|
|
1379
|
-
"operator": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
1791
|
+
"operator": "flag_evaluates_to",
|
|
1792
1792
|
"value": True,
|
|
1793
1793
|
"type": "flag",
|
|
1794
|
-
# No dependency_chain property - should
|
|
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": "
|
|
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):
|
posthoganalytics/version.py
CHANGED