Skip to content

Commit

Permalink
chore: Add hook support to contract tests (#288)
Browse files Browse the repository at this point in the history
  • Loading branch information
keelerm84 committed Apr 29, 2024
1 parent eef6e26 commit 655a087
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 7 deletions.
5 changes: 4 additions & 1 deletion contract-tests/client_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import os
import sys
import requests
from typing import Optional
from hook import PostingHook

from big_segment_store_fixture import BigSegmentStoreFixture

Expand Down Expand Up @@ -52,6 +52,9 @@ def __init__(self, tag, config):
else:
opts["send_events"] = False

if config.get("hooks") is not None:
opts["hooks"] = [PostingHook(h["name"], h["callbackUri"], h.get("data", {}), h.get("errors", {})) for h in config["hooks"]["hooks"]]

if config.get("bigSegments") is not None:
big_params = config["bigSegments"]
big_config = {
Expand Down
45 changes: 45 additions & 0 deletions contract-tests/hook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from ldclient.hook import Hook, EvaluationSeriesContext
from ldclient.evaluation import EvaluationDetail

from typing import Any, Optional
import requests


class PostingHook(Hook):
def __init__(self, name: str, callback: str, data: dict, errors: dict):
self.__name = name
self.__callback = callback
self.__data = data
self.__errors = errors

def before_evaluation(self, series_context: EvaluationSeriesContext, data: dict) -> Any:
return self.__post("beforeEvaluation", series_context, data, None)

def after_evaluation(self, series_context: EvaluationSeriesContext, data: Any, detail: EvaluationDetail) -> Any:
return self.__post("afterEvaluation", series_context, data, detail)

def __post(self, stage: str, series_context: EvaluationSeriesContext, data: Any, detail: Optional[EvaluationDetail]) -> Any:
if stage in self.__errors:
raise Exception(self.__errors[stage])

payload = {
'evaluationSeriesContext': {
'flagKey': series_context.key,
'context': series_context.context.to_dict(),
'defaultValue': series_context.default_value,
'method': series_context.method,
},
'evaluationSeriesData': data,
'stage': stage,
}

if detail is not None:
payload['evaluationDetail'] = {
'value': detail.value,
'variationIndex': detail.variation_index,
'reason': detail.reason,
}

requests.post(self.__callback, json=payload)

return {**(data or {}), **self.__data.get(stage, {})}
1 change: 1 addition & 0 deletions contract-tests/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def status():
'polling-gzip',
'inline-context',
'anonymous-redaction',
'evaluation-hooks'
]
}
return (json.dumps(body), 200, {'Content-type': 'application/json'})
Expand Down
7 changes: 3 additions & 4 deletions ldclient/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@
This submodule contains the client class that provides most of the SDK functionality.
"""

from typing import Optional, Any, Dict, Mapping, Union, Tuple, Callable, List
from typing import Optional, Any, Dict, Mapping, Tuple, Callable, List

from .impl import AnyNum

import hashlib
import hmac
import threading
import traceback
import warnings

from ldclient.config import Config
from ldclient.context import Context
Expand Down Expand Up @@ -451,7 +450,7 @@ def evaluate():
tracker = OpTracker(key, flag, context, detail, default_stage)
return _EvaluationWithHookResult(evaluation_detail=detail, results={'default_stage': default_stage, 'tracker': tracker})

hook_result = self.__evaluate_with_hooks(key=key, context=context, default_value=default_stage, method="migration_variation", block=evaluate)
hook_result = self.__evaluate_with_hooks(key=key, context=context, default_value=default_stage.value, method="migration_variation", block=evaluate)
return hook_result.results['default_stage'], hook_result.results['tracker']

def _evaluate_internal(self, key: str, context: Context, default: Any, event_factory) -> Tuple[EvaluationDetail, Optional[FeatureFlag]]:
Expand Down Expand Up @@ -652,7 +651,7 @@ def __execute_after_evaluation(self, hooks: List[Hook], series_context: Evaluati
for (hook, data) in reversed(list(zip(hooks, hook_data)))
]

def __try_execute_stage(self, method: str, hook_name: str, block: Callable[[], dict]) -> Optional[dict]:
def __try_execute_stage(self, method: str, hook_name: str, block: Callable[[], Any]) -> Any:
try:
return block()
except BaseException as e:
Expand Down
4 changes: 2 additions & 2 deletions ldclient/hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def metadata(self) -> Metadata:
return Metadata(name='UNDEFINED')

@abstractmethod
def before_evaluation(self, series_context: EvaluationSeriesContext, data: dict) -> dict:
def before_evaluation(self, series_context: EvaluationSeriesContext, data: Any) -> Any:
"""
The before method is called during the execution of a variation method
before the flag value has been determined. The method is executed
Expand All @@ -63,7 +63,7 @@ def before_evaluation(self, series_context: EvaluationSeriesContext, data: dict)
return data

@abstractmethod
def after_evaluation(self, series_context: EvaluationSeriesContext, data: dict, detail: EvaluationDetail) -> dict:
def after_evaluation(self, series_context: EvaluationSeriesContext, data: Any, detail: EvaluationDetail) -> dict:
"""
The after method is called during the execution of the variation method
after the flag value has been determined. The method is executed
Expand Down

0 comments on commit 655a087

Please sign in to comment.