Skip to content

Commit

Permalink
Merge pull request #40 from reddit/DeciderClient
Browse files Browse the repository at this point in the history
Add DeciderClient & return Decider w/ empty context if no span
  • Loading branch information
mrlevitas authored May 12, 2022
2 parents d966a78 + 555fddb commit f10f979
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 1 deletion.
51 changes: 50 additions & 1 deletion reddit_decider/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -649,12 +649,23 @@ def make_object_for_context(self, name: str, span: Span) -> Decider:

validate_decider(decider)

if span is None:
logger.debug("`span` is `None` in reddit_decider `make_object_for_context()`.")
return Decider(
decider_context=DeciderContext(user_id=""),
config_watcher=self._filewatcher,
server_span=span,
context_name=name,
event_logger=self._event_logger,
)

try:
request = span.context
ec = request.edge_context
except Exception as exc:
logger.info(f"Unable to access `request.edge_context` in `make_object_for_context()`. details: {exc}")

parsed_extracted_fields = {}
try:
if self._request_field_extractor:
extracted_fields = self._request_field_extractor(request)
Expand Down Expand Up @@ -752,6 +763,44 @@ def make_object_for_context(self, name: str, span: Span) -> Decider:
event_logger=self._event_logger,
)


class DeciderClient(config.Parser):
"""Configure a decider client.
This is meant to be used with
:py:meth:`baseplate.Baseplate.configure_context`.
See :py:func:`decider_client_from_config` for available configuration settings.
:param event_logger: The EventLogger instance to be used to log bucketing events.
:param prefix: the prefix used to filter config keys (defaults to "experiments.").
:param request_field_extractor: (optional) function used to populate fields such as
"app_name" & "build_number" in DeciderContext() via `extracted_fields` arg
"""

def __init__(
self,
event_logger: EventLogger,
prefix: str = "experiments.",
request_field_extractor: Optional[Callable[[RequestContext], Dict[str, str]]] = None
):
self._prefix = prefix
self._event_logger = event_logger
self._request_field_extractor = request_field_extractor

def parse(self, _key_path: str, raw_config: config.RawConfig) -> DeciderContextFactory:
# `_key_path` is ignored for prefix because most services will not change `app_config`
# to use "decider" key, so using `prefix` from `__init__`
return decider_client_from_config(
app_config=raw_config,
event_logger=self._event_logger,
prefix=self._prefix,
request_field_extractor=self._request_field_extractor
)


def decider_client_from_config(
app_config: config.RawConfig,
event_logger: EventLogger,
Expand All @@ -774,7 +823,7 @@ def decider_client_from_config(
``backoff`` (optional)
The base amount of time for exponential backoff when trying to find the
experiments config file. Defaults to no backoff between tries.
``request_field_extractor`` (optional) used to populate fields such as
``request_field_extractor`` (optional) function used to populate fields such as
"app_name" & "build_number" in DeciderContext() via `extracted_fields` arg
:param raw_config: The application configuration which should have
Expand Down
13 changes: 13 additions & 0 deletions tests/decider_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,19 @@ def test_make_object_for_context_and_decider_context(self, _filewatcher):
self.assertEqual(decider_event_dict["canonical_url"], CANONICAL_URL)
self.assertEqual(decider_event_dict["request"]["canonical_url"], CANONICAL_URL)

def test_make_object_for_context_and_decider_context_without_span(self, _filewatcher):
decider_ctx_factory = decider_client_from_config(
{"experiments.path": "/tmp/test", "experiments.timeout": "60 seconds"},
self.event_logger,
prefix="experiments.",
request_field_extractor=decider_field_extractor,
)
decider = decider_ctx_factory.make_object_for_context(name="test", span=None)
self.assertIsInstance(decider, Decider)

decider_ctx_dict = decider._decider_context.to_dict()
self.assertEqual(decider_ctx_dict["user_id"], "")

def test_make_object_for_context_and_decider_context_with_broken_decider_field_extractor(self, _filewatcher):
def broken_decider_field_extractor(_request: RequestContext):
return {
Expand Down

0 comments on commit f10f979

Please sign in to comment.