Skip to content

Commit 655a087

Browse files
committed
chore: Add hook support to contract tests (#288)
1 parent eef6e26 commit 655a087

File tree

5 files changed

+55
-7
lines changed

5 files changed

+55
-7
lines changed

contract-tests/client_entity.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import os
44
import sys
55
import requests
6-
from typing import Optional
6+
from hook import PostingHook
77

88
from big_segment_store_fixture import BigSegmentStoreFixture
99

@@ -52,6 +52,9 @@ def __init__(self, tag, config):
5252
else:
5353
opts["send_events"] = False
5454

55+
if config.get("hooks") is not None:
56+
opts["hooks"] = [PostingHook(h["name"], h["callbackUri"], h.get("data", {}), h.get("errors", {})) for h in config["hooks"]["hooks"]]
57+
5558
if config.get("bigSegments") is not None:
5659
big_params = config["bigSegments"]
5760
big_config = {

contract-tests/hook.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from ldclient.hook import Hook, EvaluationSeriesContext
2+
from ldclient.evaluation import EvaluationDetail
3+
4+
from typing import Any, Optional
5+
import requests
6+
7+
8+
class PostingHook(Hook):
9+
def __init__(self, name: str, callback: str, data: dict, errors: dict):
10+
self.__name = name
11+
self.__callback = callback
12+
self.__data = data
13+
self.__errors = errors
14+
15+
def before_evaluation(self, series_context: EvaluationSeriesContext, data: dict) -> Any:
16+
return self.__post("beforeEvaluation", series_context, data, None)
17+
18+
def after_evaluation(self, series_context: EvaluationSeriesContext, data: Any, detail: EvaluationDetail) -> Any:
19+
return self.__post("afterEvaluation", series_context, data, detail)
20+
21+
def __post(self, stage: str, series_context: EvaluationSeriesContext, data: Any, detail: Optional[EvaluationDetail]) -> Any:
22+
if stage in self.__errors:
23+
raise Exception(self.__errors[stage])
24+
25+
payload = {
26+
'evaluationSeriesContext': {
27+
'flagKey': series_context.key,
28+
'context': series_context.context.to_dict(),
29+
'defaultValue': series_context.default_value,
30+
'method': series_context.method,
31+
},
32+
'evaluationSeriesData': data,
33+
'stage': stage,
34+
}
35+
36+
if detail is not None:
37+
payload['evaluationDetail'] = {
38+
'value': detail.value,
39+
'variationIndex': detail.variation_index,
40+
'reason': detail.reason,
41+
}
42+
43+
requests.post(self.__callback, json=payload)
44+
45+
return {**(data or {}), **self.__data.get(stage, {})}

contract-tests/service.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ def status():
7474
'polling-gzip',
7575
'inline-context',
7676
'anonymous-redaction',
77+
'evaluation-hooks'
7778
]
7879
}
7980
return (json.dumps(body), 200, {'Content-type': 'application/json'})

ldclient/client.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@
22
This submodule contains the client class that provides most of the SDK functionality.
33
"""
44

5-
from typing import Optional, Any, Dict, Mapping, Union, Tuple, Callable, List
5+
from typing import Optional, Any, Dict, Mapping, Tuple, Callable, List
66

77
from .impl import AnyNum
88

99
import hashlib
1010
import hmac
1111
import threading
1212
import traceback
13-
import warnings
1413

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

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

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

655-
def __try_execute_stage(self, method: str, hook_name: str, block: Callable[[], dict]) -> Optional[dict]:
654+
def __try_execute_stage(self, method: str, hook_name: str, block: Callable[[], Any]) -> Any:
656655
try:
657656
return block()
658657
except BaseException as e:

ldclient/hook.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def metadata(self) -> Metadata:
4848
return Metadata(name='UNDEFINED')
4949

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

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

0 commit comments

Comments
 (0)