Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Add hook support to contract tests #288

Merged
merged 1 commit into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading