Skip to content

Commit 6e6fed5

Browse files
authored
Merge pull request #98 from reddit/add_subreddit_ad_account_id_identifiers
Add `subreddit_id`, `ad_account_id`, & `business_id` identifiers
2 parents de9233e + 1ea64bb commit 6e6fed5

File tree

4 files changed

+169
-16
lines changed

4 files changed

+169
-16
lines changed

reddit_decider/__init__.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,14 @@
3434
logger = logging.getLogger(__name__)
3535

3636
EMPLOYEE_ROLES = ["employee", "contractor"]
37-
IDENTIFIERS = ["user_id", "device_id", "canonical_url"]
37+
IDENTIFIERS = [
38+
"user_id",
39+
"device_id",
40+
"canonical_url",
41+
"subreddit_id",
42+
"ad_account_id",
43+
"business_id",
44+
]
3845
TYPE_STR_LOOKUP = {bool: "boolean", int: "integer", float: "float", str: "string", dict: "map"}
3946

4047

@@ -411,7 +418,9 @@ def get_variant_for_identifier(
411418
self,
412419
experiment_name: str,
413420
identifier: str,
414-
identifier_type: Literal["user_id", "device_id", "canonical_url"],
421+
identifier_type: Literal[
422+
"user_id", "device_id", "canonical_url", "subreddit_id", "ad_account_id", "business_id"
423+
],
415424
**exposure_kwargs: Optional[Dict[str, Any]],
416425
) -> Optional[str]:
417426
"""Return a bucketing variant, if any, with auto-exposure for a given :code:`identifier`.
@@ -471,7 +480,9 @@ def get_variant_for_identifier_without_expose(
471480
self,
472481
experiment_name: str,
473482
identifier: str,
474-
identifier_type: Literal["user_id", "device_id", "canonical_url"],
483+
identifier_type: Literal[
484+
"user_id", "device_id", "canonical_url", "subreddit_id", "ad_account_id", "business_id"
485+
],
475486
) -> Optional[str]:
476487
"""Return a bucketing variant, if any, without emitting exposure event for a given :code:`identifier`.
477488
@@ -587,7 +598,11 @@ def _decision_to_dict(self, decision: Decision) -> Dict[str, Any]:
587598
}
588599

589600
def get_all_variants_for_identifier_without_expose(
590-
self, identifier: str, identifier_type: Literal["user_id", "device_id", "canonical_url"]
601+
self,
602+
identifier: str,
603+
identifier_type: Literal[
604+
"user_id", "device_id", "canonical_url", "subreddit_id", "ad_account_id", "business_id"
605+
],
591606
) -> List[Dict[str, Union[str, int]]]:
592607
"""Return a list of experiment dicts for experiments having :code:`bucket_val` match
593608
:code:`identifier_type`, for a given :code:`identifier`, in this format:

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
-r requirements-transitive.txt
22
baseplate>=2.0.0a1,<3.0
33
black==21.4b2
4-
reddit-decider==1.2.31
4+
reddit-decider==1.2.32
55
flake8==3.9.1
66
mypy==0.790
77
pyramid==2.0 # required for `from baseplate.frameworks.pyramid import BaseplateRequest` which calls `import pyramid.events`

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
install_requires=[
2020
"baseplate>=2.0.0a1,<3.0",
2121
"reddit-edgecontext>=1.0.0a3,<2.0",
22-
"reddit-decider~=1.2.31",
22+
"reddit-decider~=1.2.32",
2323
"typing_extensions>=3.10.0.0,<5.0",
2424
],
2525
package_data={"reddit_experiments": ["py.typed"], "reddit_decider": ["py.typed"]},

tests/decider_tests.py

Lines changed: 148 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,25 @@
2424

2525
USER_ID = "t2_1234"
2626
IS_LOGGED_IN = True
27-
AUTH_CLIENT_ID = "token"
28-
COUNTRY_CODE = "US"
29-
DEVICE_ID = "abc"
30-
COOKIE_CREATED_TIMESTAMP = 1234
31-
LOID_CREATED_TIMESTAMP = 123456
32-
LOCALE_CODE = "us_en"
33-
ORIGIN_SERVICE = "origin"
27+
AD_ACCOUNT_ID = "t2_4321"
3428
APP_NAME = "ios"
3529
APP_VERSION = "0.0.0.0"
30+
AUTH_CLIENT_ID = "token"
3631
BUILD_NUMBER = 1
32+
BUSINESS_ID = "t_some"
3733
CANONICAL_URL = "www.test.com"
34+
COOKIE_CREATED_TIMESTAMP = 1234
35+
COUNTRY_CODE = "US"
36+
DEVICE_ID = "abc"
3837
EVENT_FIELDS = {
3938
"user_id": USER_ID,
4039
"logged_in": IS_LOGGED_IN,
4140
"cookie_created_timestamp": COOKIE_CREATED_TIMESTAMP,
4241
}
42+
LOID_CREATED_TIMESTAMP = 123456
43+
LOCALE_CODE = "us_en"
44+
ORIGIN_SERVICE = "origin"
45+
SUBREDDIT_ID = "t5_123abc"
4346

4447

4548
@contextlib.contextmanager
@@ -706,6 +709,90 @@ def test_get_variant_for_identifier_device_id(self):
706709
# `identifier` passed to correct event field of experiment's `bucket_val` config
707710
self.assertEqual(event_fields["device_id"], identifier)
708711

712+
def test_get_variant_for_identifier_subreddit_id(self):
713+
identifier = SUBREDDIT_ID
714+
bucket_val = "subreddit_id"
715+
self.exp_base_config["exp_1"]["experiment"].update({"bucket_val": bucket_val})
716+
717+
with create_temp_config_file(self.exp_base_config) as f:
718+
decider = setup_decider(f, self.dc, self.mock_span, self.event_logger)
719+
720+
self.assertEqual(self.event_logger.log.call_count, 0)
721+
variant = decider.get_variant_for_identifier(
722+
experiment_name="exp_1", identifier=identifier, identifier_type=bucket_val
723+
)
724+
self.assertEqual(variant, "control_1")
725+
726+
# exposure assertions
727+
self.assertEqual(self.event_logger.log.call_count, 1)
728+
event_fields = self.event_logger.log.call_args[1]
729+
self.assert_minimal_exposure_event_fields(
730+
experiment_name="exp_1",
731+
variant=variant,
732+
event_fields=event_fields,
733+
bucket_val=bucket_val,
734+
identifier=identifier,
735+
)
736+
737+
# `identifier` passed to correct event field of experiment's `bucket_val` config
738+
self.assertEqual(event_fields["subreddit_id"], identifier)
739+
740+
def test_get_variant_for_identifier_ad_account_id(self):
741+
identifier = AD_ACCOUNT_ID
742+
bucket_val = "ad_account_id"
743+
self.exp_base_config["exp_1"]["experiment"].update({"bucket_val": bucket_val})
744+
745+
with create_temp_config_file(self.exp_base_config) as f:
746+
decider = setup_decider(f, self.dc, self.mock_span, self.event_logger)
747+
748+
self.assertEqual(self.event_logger.log.call_count, 0)
749+
variant = decider.get_variant_for_identifier(
750+
experiment_name="exp_1", identifier=identifier, identifier_type=bucket_val
751+
)
752+
self.assertEqual(variant, "variant_2")
753+
754+
# exposure assertions
755+
self.assertEqual(self.event_logger.log.call_count, 1)
756+
event_fields = self.event_logger.log.call_args[1]
757+
self.assert_minimal_exposure_event_fields(
758+
experiment_name="exp_1",
759+
variant=variant,
760+
event_fields=event_fields,
761+
bucket_val=bucket_val,
762+
identifier=identifier,
763+
)
764+
765+
# `identifier` passed to correct event field of experiment's `bucket_val` config
766+
self.assertEqual(event_fields["ad_account_id"], identifier)
767+
768+
def test_get_variant_for_identifier_business_id(self):
769+
identifier = BUSINESS_ID
770+
bucket_val = "business_id"
771+
self.exp_base_config["exp_1"]["experiment"].update({"bucket_val": bucket_val})
772+
773+
with create_temp_config_file(self.exp_base_config) as f:
774+
decider = setup_decider(f, self.dc, self.mock_span, self.event_logger)
775+
776+
self.assertEqual(self.event_logger.log.call_count, 0)
777+
variant = decider.get_variant_for_identifier(
778+
experiment_name="exp_1", identifier=identifier, identifier_type=bucket_val
779+
)
780+
self.assertEqual(variant, "control_2")
781+
782+
# exposure assertions
783+
self.assertEqual(self.event_logger.log.call_count, 1)
784+
event_fields = self.event_logger.log.call_args[1]
785+
self.assert_minimal_exposure_event_fields(
786+
experiment_name="exp_1",
787+
variant=variant,
788+
event_fields=event_fields,
789+
bucket_val=bucket_val,
790+
identifier=identifier,
791+
)
792+
793+
# `identifier` passed to correct event field of experiment's `bucket_val` config
794+
self.assertEqual(event_fields["business_id"], identifier)
795+
709796
def test_get_variant_for_identifier_bogus_identifier_type(self):
710797
identifier = "anything"
711798
identifier_type = "blah"
@@ -724,7 +811,7 @@ def test_get_variant_for_identifier_bogus_identifier_type(self):
724811
self.assertEqual(variant, None)
725812

726813
assert any(
727-
"\"blah\" is not one of supported \"identifier_type\": ['user_id', 'device_id', 'canonical_url']."
814+
"\"blah\" is not one of supported \"identifier_type\": ['user_id', 'device_id', 'canonical_url', 'subreddit_id', 'ad_account_id', 'business_id']."
728815
in x.getMessage()
729816
for x in captured.records
730817
)
@@ -847,6 +934,57 @@ def test_get_variant_for_identifier_without_expose_device_id(self):
847934
# no exposures should be triggered
848935
self.assertEqual(self.event_logger.log.call_count, 0)
849936

937+
def test_get_variant_for_identifier_without_expose_subreddit_id(self):
938+
identifier = SUBREDDIT_ID
939+
bucket_val = "subreddit_id"
940+
self.exp_base_config["exp_1"]["experiment"].update({"bucket_val": bucket_val})
941+
942+
with create_temp_config_file(self.exp_base_config) as f:
943+
decider = setup_decider(f, self.dc, self.mock_span, self.event_logger)
944+
945+
self.assertEqual(self.event_logger.log.call_count, 0)
946+
variant = decider.get_variant_for_identifier_without_expose(
947+
experiment_name="exp_1", identifier=identifier, identifier_type=bucket_val
948+
)
949+
self.assertEqual(variant, "control_1")
950+
951+
# no exposures should be triggered
952+
self.assertEqual(self.event_logger.log.call_count, 0)
953+
954+
def test_get_variant_for_identifier_without_expose_ad_account_id(self):
955+
identifier = AD_ACCOUNT_ID
956+
bucket_val = "ad_account_id"
957+
self.exp_base_config["exp_1"]["experiment"].update({"bucket_val": bucket_val})
958+
959+
with create_temp_config_file(self.exp_base_config) as f:
960+
decider = setup_decider(f, self.dc, self.mock_span, self.event_logger)
961+
962+
self.assertEqual(self.event_logger.log.call_count, 0)
963+
variant = decider.get_variant_for_identifier_without_expose(
964+
experiment_name="exp_1", identifier=identifier, identifier_type=bucket_val
965+
)
966+
self.assertEqual(variant, "variant_2")
967+
968+
# no exposures should be triggered
969+
self.assertEqual(self.event_logger.log.call_count, 0)
970+
971+
def test_get_variant_for_identifier_without_expose_business_id(self):
972+
identifier = BUSINESS_ID
973+
bucket_val = "business_id"
974+
self.exp_base_config["exp_1"]["experiment"].update({"bucket_val": bucket_val})
975+
976+
with create_temp_config_file(self.exp_base_config) as f:
977+
decider = setup_decider(f, self.dc, self.mock_span, self.event_logger)
978+
979+
self.assertEqual(self.event_logger.log.call_count, 0)
980+
variant = decider.get_variant_for_identifier_without_expose(
981+
experiment_name="exp_1", identifier=identifier, identifier_type=bucket_val
982+
)
983+
self.assertEqual(variant, "control_2")
984+
985+
# no exposures should be triggered
986+
self.assertEqual(self.event_logger.log.call_count, 0)
987+
850988
def test_get_variant_for_identifier_without_expose_bogus_identifier_type(self):
851989
identifier = "anything"
852990
identifier_type = "blah"
@@ -863,7 +1001,7 @@ def test_get_variant_for_identifier_without_expose_bogus_identifier_type(self):
8631001
self.assertEqual(variant, None)
8641002

8651003
assert any(
866-
"\"blah\" is not one of supported \"identifier_type\": ['user_id', 'device_id', 'canonical_url']."
1004+
"\"blah\" is not one of supported \"identifier_type\": ['user_id', 'device_id', 'canonical_url', 'subreddit_id', 'ad_account_id', 'business_id']."
8671005
in x.getMessage()
8681006
for x in captured.records
8691007
)
@@ -1277,7 +1415,7 @@ def test_get_all_variants_for_identifier_without_expose_bogus_identifier_type(se
12771415
self.assertEqual(len(variant_arr), 0)
12781416

12791417
assert any(
1280-
"\"blah\" is not one of supported \"identifier_type\": ['user_id', 'device_id', 'canonical_url']."
1418+
"\"blah\" is not one of supported \"identifier_type\": ['user_id', 'device_id', 'canonical_url', 'subreddit_id', 'ad_account_id', 'business_id']."
12811419
in x.getMessage()
12821420
for x in captured.records
12831421
)

0 commit comments

Comments
 (0)