1313
1414from __future__ import annotations
1515
16- from typing import TYPE_CHECKING , Any , Optional
16+ from typing import TYPE_CHECKING , Any , Optional , Union
17+
18+ from optimizely .helpers .types import VariationDict
1719
1820
1921from . import decision_service
@@ -198,6 +200,67 @@ def __init__(
198200 self .decision_service = decision_service .DecisionService (self .logger , user_profile_service , self .cmab_service )
199201 self .user_profile_service = user_profile_service
200202
203+ def _get_variation_key (self , variation : Optional [Union [entities .Variation , VariationDict ]]) -> Optional [str ]:
204+ """Helper to extract variation key from either dict (holdout) or Variation object.
205+ Args:
206+ variation: Either a dict (from holdout) or entities.Variation object
207+ Returns:
208+ The variation key as a string, or None if not available
209+ """
210+ if variation is None :
211+ return None
212+
213+ try :
214+ # Try dict access first (for holdouts)
215+ if isinstance (variation , dict ):
216+ return variation .get ('key' )
217+ # Otherwise assume it's a Variation entity object
218+ else :
219+ return variation .key
220+ except (AttributeError , KeyError , TypeError ):
221+ self .logger .warning (f"Unable to extract variation key from { type (variation )} " )
222+ return None
223+
224+ def _get_variation_id (self , variation : Optional [Union [entities .Variation , VariationDict ]]) -> Optional [str ]:
225+ """Helper to extract variation id from either dict (holdout) or Variation object.
226+ Args:
227+ variation: Either a dict (from holdout) or entities.Variation object
228+ Returns:
229+ The variation id as a string, or None if not available
230+ """
231+ if variation is None :
232+ return None
233+
234+ try :
235+ # Try dict access first (for holdouts)
236+ if isinstance (variation , dict ):
237+ return variation .get ('id' )
238+ # Otherwise assume it's a Variation entity object
239+ else :
240+ return variation .id
241+ except (AttributeError , KeyError , TypeError ):
242+ self .logger .warning (f"Unable to extract variation id from { type (variation )} " )
243+ return None
244+
245+ def _get_feature_enabled (self , variation : Optional [Union [entities .Variation , VariationDict ]]) -> bool :
246+ """Helper to extract featureEnabled flag from either dict (holdout) or Variation object.
247+ Args:
248+ variation: Either a dict (from holdout) or entities.Variation object
249+ Returns:
250+ The featureEnabled value, defaults to False if not available
251+ """
252+ if variation is None :
253+ return False
254+
255+ try :
256+ if isinstance (variation , dict ):
257+ feature_enabled = variation .get ('featureEnabled' , False )
258+ return bool (feature_enabled ) if feature_enabled is not None else False
259+ else :
260+ return variation .featureEnabled if hasattr (variation , 'featureEnabled' ) else False
261+ except (AttributeError , KeyError , TypeError ):
262+ return False
263+
201264 def _validate_instantiation_options (self ) -> None :
202265 """ Helper method to validate all instantiation parameters.
203266
@@ -267,7 +330,7 @@ def _validate_user_inputs(
267330
268331 def _send_impression_event (
269332 self , project_config : project_config .ProjectConfig , experiment : Optional [entities .Experiment ],
270- variation : Optional [entities .Variation ], flag_key : str , rule_key : str , rule_type : str ,
333+ variation : Optional [Union [ entities .Variation , VariationDict ] ], flag_key : str , rule_key : str , rule_type : str ,
271334 enabled : bool , user_id : str , attributes : Optional [UserAttributes ], cmab_uuid : Optional [str ] = None
272335 ) -> None :
273336 """ Helper method to send impression event.
@@ -286,7 +349,7 @@ def _send_impression_event(
286349 if not experiment :
287350 experiment = entities .Experiment .get_default ()
288351
289- variation_id = variation . id if variation is not None else None
352+ variation_id = self . _get_variation_id ( variation ) if variation is not None else None
290353 user_event = user_event_factory .UserEventFactory .create_impression_event (
291354 project_config , experiment , variation_id ,
292355 flag_key , rule_key , rule_type ,
@@ -372,7 +435,7 @@ def _get_feature_variable_for_type(
372435
373436 if decision .variation :
374437
375- feature_enabled = decision .variation . featureEnabled
438+ feature_enabled = self . _get_feature_enabled ( decision .variation )
376439 if feature_enabled :
377440 variable_value = project_config .get_variable_value_for_variation (variable , decision .variation )
378441 self .logger .info (
@@ -393,7 +456,7 @@ def _get_feature_variable_for_type(
393456 if decision .source == enums .DecisionSources .FEATURE_TEST :
394457 source_info = {
395458 'experiment_key' : decision .experiment .key if decision .experiment else None ,
396- 'variation_key' : decision . variation . key if decision .variation else None ,
459+ 'variation_key' : self . _get_variation_key ( decision .variation ) ,
397460 }
398461
399462 try :
@@ -461,7 +524,7 @@ def _get_all_feature_variables_for_type(
461524
462525 if decision .variation :
463526
464- feature_enabled = decision .variation . featureEnabled
527+ feature_enabled = self . _get_feature_enabled ( decision .variation )
465528 if feature_enabled :
466529 self .logger .info (
467530 f'Feature "{ feature_key } " is enabled for user "{ user_id } ".'
@@ -497,7 +560,7 @@ def _get_all_feature_variables_for_type(
497560 if decision .source == enums .DecisionSources .FEATURE_TEST :
498561 source_info = {
499562 'experiment_key' : decision .experiment .key if decision .experiment else None ,
500- 'variation_key' : decision . variation . key if decision .variation else None ,
563+ 'variation_key' : self . _get_variation_key ( decision .variation ) ,
501564 }
502565
503566 self .notification_center .send_notifications (
@@ -670,7 +733,7 @@ def get_variation(
670733 variation = variation_result ['variation' ]
671734 user_profile_tracker .save_user_profile ()
672735 if variation :
673- variation_key = variation . key
736+ variation_key = self . _get_variation_key ( variation )
674737
675738 if project_config .is_feature_experiment (experiment .id ):
676739 decision_notification_type = enums .DecisionNotificationTypes .FEATURE_TEST
@@ -735,7 +798,7 @@ def is_feature_enabled(self, feature_key: str, user_id: str, attributes: Optiona
735798 is_source_rollout = decision .source == enums .DecisionSources .ROLLOUT
736799
737800 if decision .variation :
738- if decision .variation . featureEnabled is True :
801+ if self . _get_feature_enabled ( decision .variation ) is True :
739802 feature_enabled = True
740803
741804 if (is_source_rollout or not decision .variation ) and project_config .get_send_flag_decisions_value ():
@@ -748,7 +811,7 @@ def is_feature_enabled(self, feature_key: str, user_id: str, attributes: Optiona
748811 if is_source_experiment and decision .variation and decision .experiment :
749812 source_info = {
750813 'experiment_key' : decision .experiment .key ,
751- 'variation_key' : decision .variation . key ,
814+ 'variation_key' : self . _get_variation_key ( decision .variation ) ,
752815 }
753816 self ._send_impression_event (
754817 project_config , decision .experiment , decision .variation , feature .key , decision .experiment .key ,
@@ -1182,7 +1245,7 @@ def _create_optimizely_decision(
11821245 user_id = user_context .user_id
11831246 feature_enabled = False
11841247 if flag_decision .variation is not None :
1185- if flag_decision .variation . featureEnabled :
1248+ if self . _get_feature_enabled ( flag_decision .variation ) :
11861249 feature_enabled = True
11871250
11881251 self .logger .info (f'Feature { flag_key } is enabled for user { user_id } { feature_enabled } "' )
@@ -1231,11 +1294,7 @@ def _create_optimizely_decision(
12311294 all_variables [variable_key ] = actual_value
12321295
12331296 should_include_reasons = OptimizelyDecideOption .INCLUDE_REASONS in decide_options
1234- variation_key = (
1235- flag_decision .variation .key
1236- if flag_decision is not None and flag_decision .variation is not None
1237- else None
1238- )
1297+ variation_key = self ._get_variation_key (flag_decision .variation )
12391298
12401299 experiment_id = None
12411300 variation_id = None
@@ -1248,7 +1307,7 @@ def _create_optimizely_decision(
12481307
12491308 try :
12501309 if flag_decision .variation is not None :
1251- variation_id = flag_decision .variation . id
1310+ variation_id = self . _get_variation_id ( flag_decision .variation )
12521311 except AttributeError :
12531312 self .logger .warning ("flag_decision.variation has no attribute 'id'" )
12541313
0 commit comments