From 6673021b1872b61ff5fb9a2b53d049f82564a07a Mon Sep 17 00:00:00 2001
From: ozayr-zaviar <uzairzaviar@gmail.com>
Date: Wed, 22 Dec 2021 13:39:10 +0500
Subject: [PATCH 1/3] moved validate forced decision

---
 optimizely/decision_service.py        | 68 +++++++++++++++++++++++++--
 optimizely/optimizely.py              |  3 +-
 optimizely/optimizely_user_context.py | 60 -----------------------
 3 files changed, 66 insertions(+), 65 deletions(-)

diff --git a/optimizely/decision_service.py b/optimizely/decision_service.py
index e3e3079b..a1dcc599 100644
--- a/optimizely/decision_service.py
+++ b/optimizely/decision_service.py
@@ -378,8 +378,8 @@ def get_variation_for_rollout(self, project_config, feature, user):
             # check forced decision first
             rule = rollout_rules[index]
             optimizely_decision_context = OptimizelyUserContext.OptimizelyDecisionContext(feature.key, rule.key)
-            forced_decision_variation, reasons_received = user.find_validated_forced_decision(
-                optimizely_decision_context)
+            forced_decision_variation, reasons_received = self.validated_forced_decision(
+                optimizely_decision_context, user)
             decide_reasons += reasons_received
 
             if forced_decision_variation:
@@ -464,8 +464,8 @@ def get_variation_for_feature(self, project_config, feature, user_context, optio
                     optimizely_decision_context = OptimizelyUserContext.OptimizelyDecisionContext(feature.key,
                                                                                                   experiment.key)
 
-                    forced_decision_variation, reasons_received = user_context.find_validated_forced_decision(
-                        optimizely_decision_context)
+                    forced_decision_variation, reasons_received = self.validated_forced_decision(
+                        optimizely_decision_context, user_context)
                     decide_reasons += reasons_received
 
                     if forced_decision_variation:
@@ -489,3 +489,63 @@ def get_variation_for_feature(self, project_config, feature, user_context, optio
         if rollout_variation_reasons:
             decide_reasons += rollout_variation_reasons
         return variation, decide_reasons
+
+    def validated_forced_decision(self, decision_context, user_context):
+        """
+        Gets forced decisions based on flag key, rule key and variation.
+
+        Args:
+            decision context: a decision context
+            user_context context: a user context
+
+        Returns:
+            Variation of the forced decision.
+        """
+        reasons = []
+
+        forced_decision = user_context.find_forced_decision(decision_context)
+
+        flag_key = decision_context.flag_key
+        rule_key = decision_context.rule_key
+
+        if forced_decision:
+            # we use config here so we can use get_flag_variation() function which is defined in project_config
+            # otherwise we would us user_context.client instead of config
+            config = user_context.client.config_manager.get_config() if user_context.client else None
+            if not config:
+                return None, reasons
+            variation = config.get_flag_variation(flag_key, 'key', forced_decision.variation_key)
+            if variation:
+                if rule_key:
+                    user_has_forced_decision = enums.ForcedDecisionLogs \
+                        .USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED.format(forced_decision.variation_key,
+                                                                             flag_key,
+                                                                             rule_key,
+                                                                             user_context.user_id)
+
+                else:
+                    user_has_forced_decision = enums.ForcedDecisionLogs \
+                        .USER_HAS_FORCED_DECISION_WITHOUT_RULE_SPECIFIED.format(forced_decision.variation_key,
+                                                                                flag_key,
+                                                                                user_context.user_id)
+
+                reasons.append(user_has_forced_decision)
+                user_context.logger.info(user_has_forced_decision)
+
+                return variation, reasons
+
+            else:
+                if rule_key:
+                    user_has_forced_decision_but_invalid = enums.ForcedDecisionLogs \
+                        .USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID.format(flag_key,
+                                                                                         rule_key,
+                                                                                         user_context.user_id)
+                else:
+                    user_has_forced_decision_but_invalid = enums.ForcedDecisionLogs \
+                        .USER_HAS_FORCED_DECISION_WITHOUT_RULE_SPECIFIED_BUT_INVALID.format(flag_key,
+                                                                                            user_context.user_id)
+
+                reasons.append(user_has_forced_decision_but_invalid)
+                user_context.logger.info(user_has_forced_decision_but_invalid)
+
+        return None, reasons
diff --git a/optimizely/optimizely.py b/optimizely/optimizely.py
index ea68e92c..9efc9bf2 100644
--- a/optimizely/optimizely.py
+++ b/optimizely/optimizely.py
@@ -1036,7 +1036,8 @@ def _decide(self, user_context, key, decide_options=None):
 
         # Check forced decisions first
         optimizely_decision_context = OptimizelyUserContext.OptimizelyDecisionContext(flag_key=key, rule_key=rule_key)
-        forced_decision_response = user_context.find_validated_forced_decision(optimizely_decision_context)
+        forced_decision_response = self.decision_service.validated_forced_decision(optimizely_decision_context,
+                                                                                   user_context)
         variation, decision_reasons = forced_decision_response
         reasons += decision_reasons
 
diff --git a/optimizely/optimizely_user_context.py b/optimizely/optimizely_user_context.py
index 1444fe33..d8a6afb1 100644
--- a/optimizely/optimizely_user_context.py
+++ b/optimizely/optimizely_user_context.py
@@ -16,8 +16,6 @@
 import copy
 import threading
 
-from .helpers import enums
-
 
 class OptimizelyUserContext(object):
     """
@@ -225,61 +223,3 @@ def find_forced_decision(self, decision_context):
 
             # must allow None to be returned for the Flags only case
             return self.forced_decisions_map.get(decision_context)
-
-    def find_validated_forced_decision(self, decision_context):
-        """
-        Gets forced decisions based on flag key, rule key and variation.
-
-        Args:
-            decision context: a decision context
-
-        Returns:
-            Variation of the forced decision.
-        """
-        reasons = []
-
-        forced_decision = self.find_forced_decision(decision_context)
-
-        flag_key = decision_context.flag_key
-        rule_key = decision_context.rule_key
-
-        if forced_decision:
-            # we use config here so we can use get_flag_variation() function which is defined in project_config
-            # otherwise we would us self.client instead of config
-            config = self.client.config_manager.get_config() if self.client else None
-            if not config:
-                return None, reasons
-            variation = config.get_flag_variation(flag_key, 'key', forced_decision.variation_key)
-            if variation:
-                if rule_key:
-                    user_has_forced_decision = enums.ForcedDecisionLogs \
-                        .USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED.format(forced_decision.variation_key,
-                                                                             flag_key,
-                                                                             rule_key,
-                                                                             self.user_id)
-
-                else:
-                    user_has_forced_decision = enums.ForcedDecisionLogs \
-                        .USER_HAS_FORCED_DECISION_WITHOUT_RULE_SPECIFIED.format(forced_decision.variation_key,
-                                                                                flag_key,
-                                                                                self.user_id)
-
-                reasons.append(user_has_forced_decision)
-                self.logger.info(user_has_forced_decision)
-
-                return variation, reasons
-
-            else:
-                if rule_key:
-                    user_has_forced_decision_but_invalid = enums.ForcedDecisionLogs \
-                        .USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID.format(flag_key,
-                                                                                         rule_key,
-                                                                                         self.user_id)
-                else:
-                    user_has_forced_decision_but_invalid = enums.ForcedDecisionLogs \
-                        .USER_HAS_FORCED_DECISION_WITHOUT_RULE_SPECIFIED_BUT_INVALID.format(flag_key, self.user_id)
-
-                reasons.append(user_has_forced_decision_but_invalid)
-                self.logger.info(user_has_forced_decision_but_invalid)
-
-        return None, reasons

From 009974030e8eaca3ed7f8090a1ae8332e73ca325 Mon Sep 17 00:00:00 2001
From: ozayr-zaviar <uzairzaviar@gmail.com>
Date: Tue, 4 Jan 2022 18:21:03 +0500
Subject: [PATCH 2/3] headers updated

---
 optimizely/decision_service.py        | 2 +-
 optimizely/optimizely.py              | 2 +-
 optimizely/optimizely_user_context.py | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/optimizely/decision_service.py b/optimizely/decision_service.py
index a1dcc599..8f31f5f6 100644
--- a/optimizely/decision_service.py
+++ b/optimizely/decision_service.py
@@ -1,4 +1,4 @@
-# Copyright 2017-2021, Optimizely
+# Copyright 2017-2022, Optimizely
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
diff --git a/optimizely/optimizely.py b/optimizely/optimizely.py
index 9efc9bf2..5eeb592a 100644
--- a/optimizely/optimizely.py
+++ b/optimizely/optimizely.py
@@ -1,4 +1,4 @@
-# Copyright 2016-2021, Optimizely
+# Copyright 2016-2022, Optimizely
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
diff --git a/optimizely/optimizely_user_context.py b/optimizely/optimizely_user_context.py
index d8a6afb1..f096ced5 100644
--- a/optimizely/optimizely_user_context.py
+++ b/optimizely/optimizely_user_context.py
@@ -1,4 +1,4 @@
-#    Copyright 2021, Optimizely and contributors
+#    Copyright 2021-2022, Optimizely and contributors
 #
 #    Licensed under the Apache License, Version 2.0 (the "License");
 #    you may not use this file except in compliance with the License.

From e6d39af5d7d6eb4e92116eda7d03930e6fbdc401 Mon Sep 17 00:00:00 2001
From: ozayr-zaviar <uzairzaviar@gmail.com>
Date: Thu, 6 Jan 2022 19:41:24 +0500
Subject: [PATCH 3/3] comments addressed

---
 optimizely/decision_service.py | 16 +++++++---------
 optimizely/optimizely.py       |  3 ++-
 2 files changed, 9 insertions(+), 10 deletions(-)

diff --git a/optimizely/decision_service.py b/optimizely/decision_service.py
index 8f31f5f6..3aff4719 100644
--- a/optimizely/decision_service.py
+++ b/optimizely/decision_service.py
@@ -379,7 +379,7 @@ def get_variation_for_rollout(self, project_config, feature, user):
             rule = rollout_rules[index]
             optimizely_decision_context = OptimizelyUserContext.OptimizelyDecisionContext(feature.key, rule.key)
             forced_decision_variation, reasons_received = self.validated_forced_decision(
-                optimizely_decision_context, user)
+                project_config, optimizely_decision_context, user)
             decide_reasons += reasons_received
 
             if forced_decision_variation:
@@ -465,7 +465,7 @@ def get_variation_for_feature(self, project_config, feature, user_context, optio
                                                                                                   experiment.key)
 
                     forced_decision_variation, reasons_received = self.validated_forced_decision(
-                        optimizely_decision_context, user_context)
+                        project_config, optimizely_decision_context, user_context)
                     decide_reasons += reasons_received
 
                     if forced_decision_variation:
@@ -490,11 +490,12 @@ def get_variation_for_feature(self, project_config, feature, user_context, optio
             decide_reasons += rollout_variation_reasons
         return variation, decide_reasons
 
-    def validated_forced_decision(self, decision_context, user_context):
+    def validated_forced_decision(self, project_config, decision_context, user_context):
         """
         Gets forced decisions based on flag key, rule key and variation.
 
         Args:
+            project_config: a project config
             decision context: a decision context
             user_context context: a user context
 
@@ -503,18 +504,15 @@ def validated_forced_decision(self, decision_context, user_context):
         """
         reasons = []
 
-        forced_decision = user_context.find_forced_decision(decision_context)
+        forced_decision = user_context.get_forced_decision(decision_context)
 
         flag_key = decision_context.flag_key
         rule_key = decision_context.rule_key
 
         if forced_decision:
-            # we use config here so we can use get_flag_variation() function which is defined in project_config
-            # otherwise we would us user_context.client instead of config
-            config = user_context.client.config_manager.get_config() if user_context.client else None
-            if not config:
+            if not project_config:
                 return None, reasons
-            variation = config.get_flag_variation(flag_key, 'key', forced_decision.variation_key)
+            variation = project_config.get_flag_variation(flag_key, 'key', forced_decision.variation_key)
             if variation:
                 if rule_key:
                     user_has_forced_decision = enums.ForcedDecisionLogs \
diff --git a/optimizely/optimizely.py b/optimizely/optimizely.py
index 5eeb592a..10464a72 100644
--- a/optimizely/optimizely.py
+++ b/optimizely/optimizely.py
@@ -1036,7 +1036,8 @@ def _decide(self, user_context, key, decide_options=None):
 
         # Check forced decisions first
         optimizely_decision_context = OptimizelyUserContext.OptimizelyDecisionContext(flag_key=key, rule_key=rule_key)
-        forced_decision_response = self.decision_service.validated_forced_decision(optimizely_decision_context,
+        forced_decision_response = self.decision_service.validated_forced_decision(config,
+                                                                                   optimizely_decision_context,
                                                                                    user_context)
         variation, decision_reasons = forced_decision_response
         reasons += decision_reasons