-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsplit_provider.py
More file actions
206 lines (161 loc) · 9.88 KB
/
split_provider.py
File metadata and controls
206 lines (161 loc) · 9.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
import typing
import logging
import json
from openfeature.hook import Hook
from openfeature.evaluation_context import EvaluationContext
from openfeature.exception import ErrorCode, GeneralError, ParseError, OpenFeatureError, TargetingKeyMissingError
from openfeature.flag_evaluation import Reason, FlagResolutionDetails
from openfeature.provider import AbstractProvider, Metadata
from split_openfeature_provider.split_client_wrapper import SplitClientWrapper
_LOGGER = logging.getLogger(__name__)
class SplitProviderBase(AbstractProvider):
def get_metadata(self) -> Metadata:
return Metadata("Split")
def get_provider_hooks(self) -> typing.List[Hook]:
return []
def _evaluate_treatment(self, key: str, evaluation_context: EvaluationContext, default_value):
if evaluation_context is None:
raise GeneralError("Evaluation Context must be provided for the Split Provider")
if not self._split_client_wrapper.is_sdk_ready():
return SplitProvider.construct_flag_resolution(default_value, None, None, Reason.ERROR,
ErrorCode.PROVIDER_NOT_READY)
targeting_key = evaluation_context.targeting_key
if not targeting_key:
raise TargetingKeyMissingError("Missing targeting key")
attributes = SplitProvider.transform_context(evaluation_context)
evaluated = self._split_client_wrapper.split_client.get_treatment_with_config(targeting_key, key, attributes)
return self._process_treatment(evaluated, default_value)
def _process_treatment(self, evaluated, default_value):
try:
treatment = None
config = None
if evaluated != None:
treatment = evaluated[0]
config = evaluated[1]
if SplitProvider.no_treatment(treatment) or treatment == "control":
return SplitProvider.construct_flag_resolution(default_value, treatment, None, Reason.DEFAULT,
ErrorCode.FLAG_NOT_FOUND)
value = treatment
try:
if type(default_value) is int:
value = int(treatment)
elif isinstance(default_value, float):
value = float(treatment)
elif isinstance(default_value, bool):
evaluated_lower = treatment.lower()
if evaluated_lower in ["true", "on"]:
value = True
elif evaluated_lower in ["false", "off"]:
value = False
else:
raise ParseError
elif isinstance(default_value, dict):
value = json.loads(treatment)
except Exception:
raise ParseError
return SplitProvider.construct_flag_resolution(value, treatment, config)
except ParseError as ex:
_LOGGER.error("Evaluation Parse error")
_LOGGER.debug(ex)
raise ParseError("Could not convert treatment")
except OpenFeatureError as ex:
_LOGGER.error("Evaluation OpenFeature Exception")
_LOGGER.debug(ex)
raise
except Exception as ex:
_LOGGER.error("Evaluation Exception")
_LOGGER.debug(ex)
raise GeneralError("Failed to evaluate treatment")
@staticmethod
def transform_context(evaluation_context: EvaluationContext):
return evaluation_context.attributes
@staticmethod
def no_treatment(treatment: str):
return not treatment or treatment == "control"
@staticmethod
def construct_flag_resolution(value, variant, config, reason: Reason = Reason.TARGETING_MATCH,
error_code: ErrorCode = None):
return FlagResolutionDetails(value=value, error_code=error_code, reason=reason, variant=variant,
flag_metadata={"config": config})
def resolve_boolean_details(self, flag_key: str, default_value: bool,
evaluation_context: EvaluationContext = EvaluationContext()):
pass
def resolve_string_details(self, flag_key: str, default_value: str,
evaluation_context: EvaluationContext = EvaluationContext()):
pass
def resolve_integer_details(self, flag_key: str, default_value: int,
evaluation_context: EvaluationContext = EvaluationContext()):
pass
def resolve_float_details(self, flag_key: str, default_value: float,
evaluation_context: EvaluationContext = EvaluationContext()):
pass
def resolve_object_details(self, flag_key: str, default_value: dict,
evaluation_context: EvaluationContext = EvaluationContext()):
pass
async def resolve_boolean_details_async(self, flag_key: str, default_value: bool,
evaluation_context: EvaluationContext = EvaluationContext()):
pass
async def resolve_string_details_async(self, flag_key: str, default_value: str,
evaluation_context: EvaluationContext = EvaluationContext()):
pass
async def resolve_integer_details_async(self, flag_key: str, default_value: int,
evaluation_context: EvaluationContext = EvaluationContext()):
pass
async def resolve_float_details_async(self, flag_key: str, default_value: float,
evaluation_context: EvaluationContext = EvaluationContext()):
pass
async def resolve_object_details_async(self, flag_key: str, default_value: dict,
evaluation_context: EvaluationContext = EvaluationContext()):
pass
class SplitProvider(SplitProviderBase):
def __init__(self, initial_context):
self._split_client_wrapper = SplitClientWrapper(initial_context)
def resolve_boolean_details(self, flag_key: str, default_value: bool,
evaluation_context: EvaluationContext = EvaluationContext()):
return self._evaluate_treatment(flag_key, evaluation_context, default_value)
def resolve_string_details(self, flag_key: str, default_value: str,
evaluation_context: EvaluationContext = EvaluationContext()):
return self._evaluate_treatment(flag_key, evaluation_context, default_value)
def resolve_integer_details(self, flag_key: str, default_value: int,
evaluation_context: EvaluationContext = EvaluationContext()):
return self._evaluate_treatment(flag_key, evaluation_context, default_value)
def resolve_float_details(self, flag_key: str, default_value: float,
evaluation_context: EvaluationContext = EvaluationContext()):
return self._evaluate_treatment(flag_key, evaluation_context, default_value)
def resolve_object_details(self, flag_key: str, default_value: dict,
evaluation_context: EvaluationContext = EvaluationContext()):
return self._evaluate_treatment(flag_key, evaluation_context, default_value)
class SplitProviderAsync(SplitProviderBase):
def __init__(self, initial_context):
if isinstance(initial_context, dict):
initial_context["ThreadingMode"] = "asyncio"
self._split_client_wrapper = SplitClientWrapper(initial_context)
async def create(self):
await self._split_client_wrapper.create()
async def resolve_boolean_details_async(self, flag_key: str, default_value: bool,
evaluation_context: EvaluationContext = EvaluationContext()):
return await self._evaluate_treatment_async(flag_key, evaluation_context, default_value)
async def resolve_string_details_async(self, flag_key: str, default_value: str,
evaluation_context: EvaluationContext = EvaluationContext()):
return await self._evaluate_treatment_async(flag_key, evaluation_context, default_value)
async def resolve_integer_details_async(self, flag_key: str, default_value: int,
evaluation_context: EvaluationContext = EvaluationContext()):
return await self._evaluate_treatment_async(flag_key, evaluation_context, default_value)
async def resolve_float_details_async(self, flag_key: str, default_value: float,
evaluation_context: EvaluationContext = EvaluationContext()):
return await self._evaluate_treatment_async(flag_key, evaluation_context, default_value)
async def resolve_object_details_async(self, flag_key: str, default_value: dict,
evaluation_context: EvaluationContext = EvaluationContext()):
return await self._evaluate_treatment_async(flag_key, evaluation_context, default_value)
async def _evaluate_treatment_async(self, key: str, evaluation_context: EvaluationContext, default_value):
if evaluation_context is None:
raise GeneralError("Evaluation Context must be provided for the Split Provider")
if not await self._split_client_wrapper.is_sdk_ready_async():
return SplitProvider.construct_flag_resolution(default_value, None, None, Reason.ERROR,
ErrorCode.PROVIDER_NOT_READY)
targeting_key = evaluation_context.targeting_key
if not targeting_key:
raise TargetingKeyMissingError("Missing targeting key")
attributes = SplitProvider.transform_context(evaluation_context)
evaluated = await self._split_client_wrapper.split_client.get_treatment_with_config(targeting_key, key, attributes)
return self._process_treatment(evaluated, default_value)