Skip to content

Commit b087fc5

Browse files
authored
Merge pull request #83 from reddit/use_RustDecider_choose_all
Use `RustDecider#choose_all()` in `get_all_variants_without_expose()` & `get_all_variants_for_identifier_without_expose()`
2 parents d42bb98 + ea55098 commit b087fc5

File tree

6 files changed

+75
-68
lines changed

6 files changed

+75
-68
lines changed

docs/index.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ Upgrade or integrate reddit-experiments package:
6868
.. code-block:: python
6969
7070
# import latest reddit-experiments package in service requirements.txt
71-
reddit-experiments>=1.3.9
71+
reddit-experiments>=1.3.11
7272
7373
Initialize :code:`decider` instance on Baseplate context
7474
--------------------------------------------------------
@@ -126,7 +126,7 @@ Make sure :code:`EdgeContext` is accessible on :code:`request` object like so:
126126
# - locale
127127
# - origin_service
128128
# - is_employee
129-
# - loid_created_ms (>=1.3.9)
129+
# - loid_created_ms (>=1.3.11)
130130
131131
# Customized fields can be defined below to be extracted from a baseplate request
132132
# and will override above edge_context fields.

docs/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ docutils==0.16
44
Jinja2==2.11.2
55
MarkupSafe==1.1.1
66
pydocstyle==5.1.1
7-
reddit-decider==1.2.15
7+
reddit-decider==1.2.29
88
reddit-edgecontext==1.0.0a3
99
Sphinx==3.4.0
1010
sphinx-autodoc-typehints==1.11.1

reddit_decider/__init__.py

Lines changed: 35 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from reddit_edgecontext import ValidatedAuthenticationToken
2525
from rust_decider import Decider as RustDecider
2626
from rust_decider import DeciderException
27+
from rust_decider import Decision
2728
from rust_decider import FeatureNotFoundException
2829
from rust_decider import make_ctx
2930
from typing_extensions import Literal
@@ -610,47 +611,40 @@ def get_all_variants_without_expose(self) -> List[Dict[str, Union[str, int]]]:
610611
611612
:return: list of experiment dicts with non-:code:`None` variants.
612613
"""
613-
decider = self._get_decider()
614-
if decider is None:
615-
return []
616-
617-
ctx = self._get_ctx()
618-
ctx_err = ctx.err()
619-
if ctx_err is not None:
620-
logger.info(f"Encountered error in rust_decider.make_ctx(): {ctx_err}")
614+
if self._internal is None:
615+
logger.error("rs_decider is None--did not initialize.")
621616
return []
622617

623-
all_decisions_result = decider.choose_all(ctx)
618+
ctx = self._decider_context.to_dict()
624619

625-
error = all_decisions_result.err()
626-
if error:
627-
logger.info(f"Encountered error in decider.choose_all(): {error}")
620+
try:
621+
all_decisions = self._internal.choose_all(ctx)
622+
except DeciderException as exc:
623+
logger.info(str(exc))
628624
return []
629625

630-
all_decisions = all_decisions_result.decisions()
631626
parsed_choices = []
632627

633628
event_context_fields = self._decider_context.to_event_dict()
634629

635-
for exp_name, decision in all_decisions.items():
636-
decision_error = decision.err()
637-
if decision_error:
638-
logger.info(
639-
f"Encountered error for experiment: {exp_name} in decider.choose_all(): {decision_error}"
640-
)
641-
continue
642-
643-
decision_dict = decision.decision_dict()
644-
645-
if decision_dict:
646-
parsed_choices.append(self._format_decision(decision_dict))
630+
for decision in all_decisions.values():
631+
if decision.variant:
632+
parsed_choices.append(self._decision_to_dict(decision))
647633

648634
# expose Holdout if the experiment is part of one
649-
for event in decision.events():
635+
for event in decision.events:
650636
self._send_expose_if_holdout(event=event, exposure_fields=event_context_fields)
651637

652638
return parsed_choices
653639

640+
def _decision_to_dict(self, decision: Decision) -> Dict[str, Any]:
641+
return {
642+
"name": decision.variant,
643+
"id": decision.feature_id,
644+
"version": str(decision.feature_version),
645+
"experimentName": decision.feature_name,
646+
}
647+
654648
def get_all_variants_for_identifier_without_expose(
655649
self, identifier: str, identifier_type: Literal["user_id", "device_id", "canonical_url"]
656650
) -> List[Dict[str, Union[str, int]]]:
@@ -691,48 +685,32 @@ def get_all_variants_for_identifier_without_expose(
691685
)
692686
return []
693687

694-
decider = self._get_decider()
695-
if decider is None:
696-
return []
697-
698-
ctx = self._get_ctx_with_set_identifier(
699-
identifier=identifier, identifier_type=identifier_type
700-
)
701-
ctx_err = ctx.err()
702-
if ctx_err is not None:
703-
logger.info(f"Encountered error in rust_decider.make_ctx(): {ctx_err}")
688+
if self._internal is None:
689+
logger.error("rs_decider is None--did not initialize.")
704690
return []
705691

706-
all_decisions_result = decider.choose_all(ctx=ctx, identifier_type=identifier_type)
692+
ctx = self._decider_context.to_dict()
693+
ctx[identifier_type] = identifier
707694

708-
error = all_decisions_result.err()
709-
if error:
710-
logger.info(f"Encountered error in decider.choose_all(): {error}")
695+
try:
696+
all_decisions = self._internal.choose_all(
697+
context=ctx, bucketing_field_filter=identifier_type
698+
)
699+
except DeciderException as exc:
700+
logger.info(exc)
711701
return []
712702

713-
all_decisions = all_decisions_result.decisions()
714703
parsed_choices = []
715704

716705
event_context_fields = self._decider_context.to_event_dict()
717706

718-
for exp_name, decision in all_decisions.items():
719-
decision_error = decision.err()
720-
if decision_error:
721-
logger.info(
722-
f"Encountered error for experiment: {exp_name} in decider.choose_all(): {decision_error}"
723-
)
724-
continue
725-
726-
decision_dict = decision.decision_dict()
727-
728-
if decision_dict:
729-
parsed_choices.append(self._format_decision(decision_dict))
707+
for decision in all_decisions.values():
708+
if decision.variant:
709+
parsed_choices.append(self._decision_to_dict(decision))
730710

731711
# expose Holdout if the experiment is part of one
732-
for event in decision.events():
733-
self._send_expose_if_holdout(
734-
event=event, exposure_fields=event_context_fields, overwrite_identifier=True
735-
)
712+
for event in decision.events:
713+
self._send_expose_if_holdout(event=event, exposure_fields=event_context_fields)
736714

737715
return parsed_choices
738716

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
33
black==21.4b2
4-
reddit-decider==1.2.28
4+
reddit-decider==1.2.29
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.28",
22+
"reddit-decider~=1.2.29",
2323
"typing_extensions>=3.10.0.0,<5.0",
2424
],
2525
package_data={"reddit_experiments": ["py.typed"]},

tests/decider_tests.py

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -972,6 +972,36 @@ def test_get_all_variants_without_expose_with_hg(self):
972972
experiment_name="hg", variant="holdout", event_fields=event_fields
973973
)
974974

975+
def test_get_all_variants_without_expose_ctx_missing_device_id(self):
976+
# no device_id in ctx
977+
dc = DeciderContext(user_id=USER_ID)
978+
979+
self.exp_base_config["exp_1"]["experiment"].update({"bucket_val": "device_id"})
980+
self.exp_base_config.update(self.additional_two_exp)
981+
982+
with create_temp_config_file(self.exp_base_config) as f:
983+
decider = setup_decider(f, dc, self.mock_span, self.event_logger)
984+
985+
self.assertEqual(self.event_logger.log.call_count, 0)
986+
987+
decision_arr = decider.get_all_variants_without_expose()
988+
989+
# device_id experiment not bucketed since
990+
# device_id is missing in ctx
991+
self.assertEqual(len(decision_arr), 2)
992+
993+
self.assertEqual(
994+
first_occurrence_of_key_in(decision_arr, "experimentName", "e1"),
995+
{"id": 6, "name": "e1treat", "version": "4", "experimentName": "e1"},
996+
)
997+
self.assertEqual(
998+
first_occurrence_of_key_in(decision_arr, "experimentName", "e2"),
999+
{"id": 7, "name": "e2treat", "version": "5", "experimentName": "e2"},
1000+
)
1001+
1002+
# no exposures should be triggered
1003+
self.assertEqual(self.event_logger.log.call_count, 0)
1004+
9751005
def test_get_all_variants_for_identifier_without_expose_user_id(self):
9761006
identifier = USER_ID
9771007
bucket_val = "user_id"
@@ -1182,7 +1212,9 @@ def test_get_all_variants_for_identifier_without_expose_canonical_url(self):
11821212
# add 2 more experiments
11831213
self.exp_base_config.update(self.additional_two_exp)
11841214

1185-
for exp_name in self.exp_base_config.keys():
1215+
# update 2 of 3 experiments to have bucket_val: 'canonical_url'
1216+
# so that the 3rd one is filtered out
1217+
for exp_name in list(self.exp_base_config.keys())[0:2]:
11861218
self.exp_base_config[exp_name]["experiment"].update({"bucket_val": bucket_val})
11871219

11881220
with create_temp_config_file(self.exp_base_config) as f:
@@ -1193,7 +1225,8 @@ def test_get_all_variants_for_identifier_without_expose_canonical_url(self):
11931225
identifier=identifier, identifier_type=bucket_val
11941226
)
11951227

1196-
self.assertEqual(len(variant_arr), len(self.exp_base_config))
1228+
# non-canonical_url experiment is not included in result
1229+
self.assertEqual(len(variant_arr), 2)
11971230
self.assertEqual(
11981231
first_occurrence_of_key_in(variant_arr, "experimentName", "exp_1"),
11991232
{"id": 1, "name": "variant_3", "version": "2", "experimentName": "exp_1"},
@@ -1202,10 +1235,6 @@ def test_get_all_variants_for_identifier_without_expose_canonical_url(self):
12021235
first_occurrence_of_key_in(variant_arr, "experimentName", "e1"),
12031236
{"id": 6, "name": "e1treat", "version": "4", "experimentName": "e1"},
12041237
)
1205-
self.assertEqual(
1206-
first_occurrence_of_key_in(variant_arr, "experimentName", "e2"),
1207-
{"id": 7, "name": "e2treat", "version": "5", "experimentName": "e2"},
1208-
)
12091238

12101239
# no exposures should be triggered
12111240
self.assertEqual(self.event_logger.log.call_count, 0)

0 commit comments

Comments
 (0)