From 219ac08dd75faba260a8458ae0de9668f6e569ef Mon Sep 17 00:00:00 2001 From: eric Date: Tue, 13 Sep 2022 16:43:59 -0400 Subject: [PATCH 1/9] add date handling to local eval --- posthog/feature_flags.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/posthog/feature_flags.py b/posthog/feature_flags.py index 11a44e4e..dff6d2c2 100644 --- a/posthog/feature_flags.py +++ b/posthog/feature_flags.py @@ -1,6 +1,9 @@ import hashlib import re +import datetime +from dateutil import parser + from posthog.utils import is_valid_regex __LONG_SCALE__ = float(0xFFFFFFFFFFFFFFF) @@ -126,4 +129,31 @@ def match_property(property, property_values) -> bool: if operator == "lte": return type(override_value) == type(value) and override_value <= value + if operator == "is_date_before": + try: + parsed_date = parser.parse(value) + except: + raise InconclusiveMatchError("The date set on the flag is not a valid format") + + if isinstance(override_value, datetime.date): + return override_value < parsed_date + elif isinstance(override_value, str): + return parser.parse(override_value) < parsed_date + else: + raise InconclusiveMatchError("The date provided to override must be a string or date object") + + if operator == "is_date_after": + try: + parsed_date = parser.parse(value) + except: + raise InconclusiveMatchError("The date set on the flag is not a valid format") + + if isinstance(override_value, datetime.date): + return override_value < parsed_date + elif isinstance(override_value, str): + return parser.parse(override_value) > parsed_date + else: + raise InconclusiveMatchError("The date provided to override must be a string or date object") + + return False From cefa46528ada15e83c5997bcf3d5a2b9f67ec1ef Mon Sep 17 00:00:00 2001 From: eric Date: Tue, 13 Sep 2022 16:51:23 -0400 Subject: [PATCH 2/9] add test --- posthog/feature_flags.py | 2 +- posthog/test/test_feature_flags.py | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/posthog/feature_flags.py b/posthog/feature_flags.py index dff6d2c2..50f403a7 100644 --- a/posthog/feature_flags.py +++ b/posthog/feature_flags.py @@ -149,7 +149,7 @@ def match_property(property, property_values) -> bool: raise InconclusiveMatchError("The date set on the flag is not a valid format") if isinstance(override_value, datetime.date): - return override_value < parsed_date + return override_value > parsed_date elif isinstance(override_value, str): return parser.parse(override_value) > parsed_date else: diff --git a/posthog/test/test_feature_flags.py b/posthog/test/test_feature_flags.py index 2f07c521..d4a22be0 100644 --- a/posthog/test/test_feature_flags.py +++ b/posthog/test/test_feature_flags.py @@ -3,6 +3,7 @@ import mock from freezegun import freeze_time +from dateutil import parser from posthog.client import Client from posthog.feature_flags import InconclusiveMatchError, match_property from posthog.request import APIError @@ -1118,6 +1119,28 @@ def test_match_properties_math_operators(self): self.assertFalse(match_property(property_d, {"key": "44"})) self.assertFalse(match_property(property_d, {"key": 44})) + def test_match_property_date_operators(self): + property_a = self.property(key="key", value="2022-05-01", operator="is_date_before") + self.assertTrue(match_property(property_a, {"key": "2022-03-01"})) + self.assertTrue(match_property(property_a, {"key": "2022-04-30"})) + self.assertTrue(match_property(property_a, {"key": parser.parse("2022-04-30")})) + self.assertFalse(match_property(property_a, {"key": "2022-05-30"})) + + # Can't be a number + with self.assertRaises(InconclusiveMatchError): + match_property(property_a, {"key": 1}) + + property_b = self.property(key="key", value="2022-05-01", operator="is_date_after") + self.assertTrue(match_property(property_b, {"key": "2022-05-02"})) + self.assertTrue(match_property(property_b, {"key": "2022-05-30"})) + self.assertTrue(match_property(property_b, {"key": parser.parse("2022-05-30")})) + self.assertFalse(match_property(property_b, {"key": "2022-04-30"})) + + # Invalid flag proeprty + property_c = self.property(key="key", value=1234, operator="is_date_before") + + with self.assertRaises(InconclusiveMatchError): + match_property(property_c, {"key": 1}) class TestCaptureCalls(unittest.TestCase): @mock.patch.object(Client, "capture") From 8d2255c7e4e4ff38e01d9c77a1df31c4432417d9 Mon Sep 17 00:00:00 2001 From: eric Date: Tue, 13 Sep 2022 16:53:37 -0400 Subject: [PATCH 3/9] formatted --- posthog/feature_flags.py | 1 - posthog/test/test_feature_flags.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/posthog/feature_flags.py b/posthog/feature_flags.py index 50f403a7..f91e2f75 100644 --- a/posthog/feature_flags.py +++ b/posthog/feature_flags.py @@ -155,5 +155,4 @@ def match_property(property, property_values) -> bool: else: raise InconclusiveMatchError("The date provided to override must be a string or date object") - return False diff --git a/posthog/test/test_feature_flags.py b/posthog/test/test_feature_flags.py index d4a22be0..d41c8bbe 100644 --- a/posthog/test/test_feature_flags.py +++ b/posthog/test/test_feature_flags.py @@ -1142,6 +1142,7 @@ def test_match_property_date_operators(self): with self.assertRaises(InconclusiveMatchError): match_property(property_c, {"key": 1}) + class TestCaptureCalls(unittest.TestCase): @mock.patch.object(Client, "capture") @mock.patch("posthog.client.decide") From 6aeea79cf5d29ffb6a6c71405138a0ded1c43b96 Mon Sep 17 00:00:00 2001 From: eric Date: Tue, 13 Sep 2022 20:30:58 -0400 Subject: [PATCH 4/9] fix imports --- posthog/feature_flags.py | 2 +- posthog/test/test_feature_flags.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/posthog/feature_flags.py b/posthog/feature_flags.py index f91e2f75..1d274a8d 100644 --- a/posthog/feature_flags.py +++ b/posthog/feature_flags.py @@ -1,7 +1,7 @@ +import datetime import hashlib import re -import datetime from dateutil import parser from posthog.utils import is_valid_regex diff --git a/posthog/test/test_feature_flags.py b/posthog/test/test_feature_flags.py index d41c8bbe..8c1405ff 100644 --- a/posthog/test/test_feature_flags.py +++ b/posthog/test/test_feature_flags.py @@ -1,9 +1,9 @@ import unittest import mock +from dateutil import parser from freezegun import freeze_time -from dateutil import parser from posthog.client import Client from posthog.feature_flags import InconclusiveMatchError, match_property from posthog.request import APIError From 82f88f92719fb46c9167b37e159f2d1ac5a5e933 Mon Sep 17 00:00:00 2001 From: Neil Kakkar Date: Wed, 14 Sep 2022 13:33:10 +0100 Subject: [PATCH 5/9] handle invalid input from user case --- posthog/feature_flags.py | 20 ++++++++++++++------ posthog/test/test_feature_flags.py | 13 ++++++++++++- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/posthog/feature_flags.py b/posthog/feature_flags.py index 1d274a8d..72df82b0 100644 --- a/posthog/feature_flags.py +++ b/posthog/feature_flags.py @@ -132,27 +132,35 @@ def match_property(property, property_values) -> bool: if operator == "is_date_before": try: parsed_date = parser.parse(value) - except: + except Exception: raise InconclusiveMatchError("The date set on the flag is not a valid format") if isinstance(override_value, datetime.date): return override_value < parsed_date elif isinstance(override_value, str): - return parser.parse(override_value) < parsed_date + try: + override_date = parser.parse(override_value) + return override_date < parsed_date + except Exception: + raise InconclusiveMatchError("The date provided is not a valid format") else: - raise InconclusiveMatchError("The date provided to override must be a string or date object") + raise InconclusiveMatchError("The date provided must be a string or date object") if operator == "is_date_after": try: parsed_date = parser.parse(value) - except: + except Exception: raise InconclusiveMatchError("The date set on the flag is not a valid format") if isinstance(override_value, datetime.date): return override_value > parsed_date elif isinstance(override_value, str): - return parser.parse(override_value) > parsed_date + try: + override_date = parser.parse(override_value) + return override_date > parsed_date + except Exception: + raise InconclusiveMatchError("The date provided is not a valid format") else: - raise InconclusiveMatchError("The date provided to override must be a string or date object") + raise InconclusiveMatchError("The date provided must be a string or date object") return False diff --git a/posthog/test/test_feature_flags.py b/posthog/test/test_feature_flags.py index 8c1405ff..776d7d99 100644 --- a/posthog/test/test_feature_flags.py +++ b/posthog/test/test_feature_flags.py @@ -1,3 +1,4 @@ +from datetime import datetime import unittest import mock @@ -1123,20 +1124,30 @@ def test_match_property_date_operators(self): property_a = self.property(key="key", value="2022-05-01", operator="is_date_before") self.assertTrue(match_property(property_a, {"key": "2022-03-01"})) self.assertTrue(match_property(property_a, {"key": "2022-04-30"})) + self.assertTrue(match_property(property_a, {"key": datetime(2022, 4, 30)})) self.assertTrue(match_property(property_a, {"key": parser.parse("2022-04-30")})) self.assertFalse(match_property(property_a, {"key": "2022-05-30"})) # Can't be a number with self.assertRaises(InconclusiveMatchError): match_property(property_a, {"key": 1}) + + # can't be invalid string + with self.assertRaises(InconclusiveMatchError): + match_property(property_a, {"key": "abcdef"}) property_b = self.property(key="key", value="2022-05-01", operator="is_date_after") self.assertTrue(match_property(property_b, {"key": "2022-05-02"})) self.assertTrue(match_property(property_b, {"key": "2022-05-30"})) + self.assertTrue(match_property(property_b, {"key": datetime(2022, 5, 30)})) self.assertTrue(match_property(property_b, {"key": parser.parse("2022-05-30")})) self.assertFalse(match_property(property_b, {"key": "2022-04-30"})) - # Invalid flag proeprty + # can't be invalid string + with self.assertRaises(InconclusiveMatchError): + match_property(property_b, {"key": "abcdef"}) + + # Invalid flag property property_c = self.property(key="key", value=1234, operator="is_date_before") with self.assertRaises(InconclusiveMatchError): From b9bbb2f47053bc12cc4e6cef2f9c78a2c81024a9 Mon Sep 17 00:00:00 2001 From: Neil Kakkar Date: Wed, 14 Sep 2022 13:36:22 +0100 Subject: [PATCH 6/9] black --- posthog/test/test_feature_flags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posthog/test/test_feature_flags.py b/posthog/test/test_feature_flags.py index 776d7d99..6dd9b805 100644 --- a/posthog/test/test_feature_flags.py +++ b/posthog/test/test_feature_flags.py @@ -1131,7 +1131,7 @@ def test_match_property_date_operators(self): # Can't be a number with self.assertRaises(InconclusiveMatchError): match_property(property_a, {"key": 1}) - + # can't be invalid string with self.assertRaises(InconclusiveMatchError): match_property(property_a, {"key": "abcdef"}) From b18cb983ebe3151a774086244816ff1b52990360 Mon Sep 17 00:00:00 2001 From: Neil Kakkar Date: Wed, 14 Sep 2022 13:42:08 +0100 Subject: [PATCH 7/9] isort --- posthog/test/test_feature_flags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posthog/test/test_feature_flags.py b/posthog/test/test_feature_flags.py index 6dd9b805..3168c636 100644 --- a/posthog/test/test_feature_flags.py +++ b/posthog/test/test_feature_flags.py @@ -1,5 +1,5 @@ -from datetime import datetime import unittest +from datetime import datetime import mock from dateutil import parser From b0435d194b5e7e00dbde1a027b61e2271ff23f4c Mon Sep 17 00:00:00 2001 From: Neil Kakkar Date: Wed, 14 Sep 2022 13:43:48 +0100 Subject: [PATCH 8/9] bump version & changelog --- CHANGELOG.md | 5 +++++ posthog/version.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b33b414c..331b84c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.1.1 - 2022-09-14 + +Changes: + +1. Feature flags local evaluation now supports date property filters as well. ## 2.1.0 - 2022-08-11 Changes: diff --git a/posthog/version.py b/posthog/version.py index 8b5a2a80..9c9ab081 100644 --- a/posthog/version.py +++ b/posthog/version.py @@ -1,4 +1,4 @@ -VERSION = "2.1.0" +VERSION = "2.1.1" if __name__ == "__main__": print(VERSION, end="") From 29117c681840509bdddc6c40e12e04aeb5264f65 Mon Sep 17 00:00:00 2001 From: Neil Kakkar Date: Wed, 14 Sep 2022 13:51:38 +0100 Subject: [PATCH 9/9] combine conditions --- posthog/feature_flags.py | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/posthog/feature_flags.py b/posthog/feature_flags.py index 72df82b0..c7e9de6a 100644 --- a/posthog/feature_flags.py +++ b/posthog/feature_flags.py @@ -129,35 +129,24 @@ def match_property(property, property_values) -> bool: if operator == "lte": return type(override_value) == type(value) and override_value <= value - if operator == "is_date_before": + if operator in ["is_date_before", "is_date_after"]: try: parsed_date = parser.parse(value) except Exception: raise InconclusiveMatchError("The date set on the flag is not a valid format") if isinstance(override_value, datetime.date): - return override_value < parsed_date + if operator == "is_date_before": + return override_value < parsed_date + else: + return override_value > parsed_date elif isinstance(override_value, str): try: override_date = parser.parse(override_value) - return override_date < parsed_date - except Exception: - raise InconclusiveMatchError("The date provided is not a valid format") - else: - raise InconclusiveMatchError("The date provided must be a string or date object") - - if operator == "is_date_after": - try: - parsed_date = parser.parse(value) - except Exception: - raise InconclusiveMatchError("The date set on the flag is not a valid format") - - if isinstance(override_value, datetime.date): - return override_value > parsed_date - elif isinstance(override_value, str): - try: - override_date = parser.parse(override_value) - return override_date > parsed_date + if operator == "is_date_before": + return override_date < parsed_date + else: + return override_date > parsed_date except Exception: raise InconclusiveMatchError("The date provided is not a valid format") else: