Пример #1
0
 def initialize(self):
     """Initialize TargetClient"""
     if requires_decisioning_engine(self.config.get("decisioning_method")):
         try:
             request_location_hint_cookie(
                 self, self.config.get("target_location_hint"))
             decisioning_config = DecisioningConfig(
                 self.config.get("client"),
                 self.config.get("organization_id"),
                 polling_interval=self.config.get("polling_interval"),
                 artifact_location=self.config.get("artifact_location"),
                 artifact_payload=self.config.get("artifact_payload"),
                 environment=self.config.get("environment"),
                 cdn_environment=self.config.get("cdn_environment"),
                 cdn_base_path=self.config.get("cdn_base_path"),
                 send_notification_func=self.send_notifications,
                 telemetry_enabled=self.config.get("telemetry_enabled"),
                 event_emitter=self.event_emitter,
                 maximum_wait_ready=self.config.get("maximum_wait_ready"),
                 property_token=self.config.get("property_token"))
             self.decisioning_engine = TargetDecisioningEngine(
                 decisioning_config)
             self.decisioning_engine.initialize()
             self.event_emitter(CLIENT_READY)
         except Exception as err:
             self.logger.error(
                 "Unable to initialize TargetDecisioningEngine: \n {}".
                 format(str(err)))
     else:
         # Should emit client_ready event after client gets returned by TargetClient.create
         timer = Timer(CLIENT_READY_DELAY, self.event_emitter,
                       [CLIENT_READY])
         timer.start()
Пример #2
0
    def test_initialize(self):
        config = deepcopy(CONFIG)
        config.polling_interval = 0
        self.decisioning = TargetDecisioningEngine(config)

        with patch.object(PoolManager,
                          "request",
                          return_value=MOCK_ARTIFACT_RESPONSE):
            self.decisioning.initialize()

        self.assertEqual(self.decisioning.artifact, ARTIFACT_BLANK)
        self.assertIsNotNone(self.decisioning._artifact_provider)
        self.assertEqual(
            self.decisioning._artifact_provider.subscription_count, 2)
Пример #3
0
    def test_get_offers(self):
        config = deepcopy(CONFIG)
        config.polling_interval = 0
        self.decisioning = TargetDecisioningEngine(config)

        with patch.object(PoolManager,
                          "request",
                          return_value=MOCK_ARTIFACT_RESPONSE):
            self.decisioning.initialize()

        get_offers_opts = TargetDeliveryRequest(request=TARGET_REQUEST,
                                                session_id="dummy_session")
        offers = self.decisioning.get_offers(get_offers_opts)
        self.assertIsNotNone(offers)
Пример #4
0
    def test_initialize_updates_artifact_on_polling_interval(self):
        config = deepcopy(CONFIG)
        config.polling_interval = 1  # seconds
        self.decisioning = TargetDecisioningEngine(config)

        with patch(
                "target_decisioning_engine.artifact_provider.get_min_polling_interval",
                return_value=0):
            with patch.object(PoolManager,
                              "request",
                              return_value=MOCK_ARTIFACT_RESPONSE
                              ) as artifact_request_mock:
                self.decisioning.initialize()

                time.sleep(5)

                self.assertGreaterEqual(artifact_request_mock.call_count, 4)
                artifact = self.decisioning.get_raw_artifact()
                self.assertIsNotNone(artifact)
Пример #5
0
    def test_get_offers_unsupported_artifact_version(self):
        config = deepcopy(CONFIG)
        config.polling_interval = 0
        self.decisioning = TargetDecisioningEngine(config)

        with patch.object(
                PoolManager,
                "request",
                return_value=MOCK_ARTIFACT_RESPONSE_UNSUPPORTED_VERSION):
            self.decisioning.initialize()

        with self.assertRaises(Exception) as err:
            get_offers_opts = TargetDeliveryRequest(request=TARGET_REQUEST,
                                                    session_id="dummy_session")
            self.decisioning.get_offers(get_offers_opts)
            self.assertEqual(
                json.loads(err.exception.body),
                MESSAGES.get("ARTIFACT_VERSION_UNSUPPORTED")(
                    ARTIFACT_UNSUPPORTED_VERSION.get("version"),
                    SUPPORTED_ARTIFACT_MAJOR_VERSION))
Пример #6
0
    def test_initialize_error_fetching_artifact(self):
        config = deepcopy(CONFIG)
        config.polling_interval = 0
        config.event_emitter = Mock()
        self.decisioning = TargetDecisioningEngine(config)

        with patch.object(PoolManager,
                          "request",
                          return_value=MOCK_ARTIFACT_RESPONSE_BAD):
            with patch(
                    "target_decisioning_engine.artifact_provider.BACKOFF_FACTOR",
                    0):
                with self.assertRaises(Exception) as err:
                    self.decisioning.initialize()
                    self.assertEqual(
                        json.loads(err.exception.body), {
                            "status": 500,
                            "message":
                            "The decisioning artifact is not available"
                        })
                    self.assertEqual(config.event_emitter.call_count,
                                     11)  # validate retries
                    self.assertEqual(config.event_emitter.call_args[0][0],
                                     ARTIFACT_DOWNLOAD_FAILED)
Пример #7
0
class TargetClient:
    """External-facing Target client for handling personalization"""
    def __init__(self, options):
        """TargetClient constructor"""
        if not options or not options.get("internal"):
            raise Exception(MESSAGES.get("PRIVATE_CONSTRUCTOR"))

        self.config = dict(options)
        self.config["timeout"] = options.get("timeout") if options.get("timeout") \
            else DEFAULT_TIMEOUT
        self.logger = get_logger(options.get("logger"))
        self.event_emitter = EventProvider(self.config.get("events")).emit
        self.decisioning_engine = None

    def initialize(self):
        """Initialize TargetClient"""
        if requires_decisioning_engine(self.config.get("decisioning_method")):
            try:
                request_location_hint_cookie(
                    self, self.config.get("target_location_hint"))
                decisioning_config = DecisioningConfig(
                    self.config.get("client"),
                    self.config.get("organization_id"),
                    polling_interval=self.config.get("polling_interval"),
                    artifact_location=self.config.get("artifact_location"),
                    artifact_payload=self.config.get("artifact_payload"),
                    environment=self.config.get("environment"),
                    cdn_environment=self.config.get("cdn_environment"),
                    cdn_base_path=self.config.get("cdn_base_path"),
                    send_notification_func=self.send_notifications,
                    telemetry_enabled=self.config.get("telemetry_enabled"),
                    event_emitter=self.event_emitter,
                    maximum_wait_ready=self.config.get("maximum_wait_ready"),
                    property_token=self.config.get("property_token"))
                self.decisioning_engine = TargetDecisioningEngine(
                    decisioning_config)
                self.decisioning_engine.initialize()
                self.event_emitter(CLIENT_READY)
            except Exception as err:
                self.logger.error(
                    "Unable to initialize TargetDecisioningEngine: \n {}".
                    format(str(err)))
        else:
            # Should emit client_ready event after client gets returned by TargetClient.create
            timer = Timer(CLIENT_READY_DELAY, self.event_emitter,
                          [CLIENT_READY])
            timer.start()

    @staticmethod
    def create(options=None):
        """The TargetClient creation factory method
        :param options: (dict) TargetClient options, required

        options.client: (str) Target Client Id, required

        options.organization_id: (str) Target Organization Id, required

        options.timeout: (int) Target request timeout in ms, default: 3000

        options.server_domain: (str) Server domain, optional

        options.target_location_hint: (str) Target Location Hint, optional

        options.secure: (bool) Unset to enforce HTTP scheme, default: true

        options.logger: (dict) Replaces the default INFO level logger, optional

        options.decisioning_method: ("on-device"|"server-side"|"hybrid")
            The decisioning method, defaults to remote, optional

        options.polling_interval: (int) Local Decisioning -
            Polling interval in ms, default: 30000

        options.maximum_wait_ready: (int) Local Decisioning - The maximum amount of time (in ms)
            to wait for clientReady.  Default is to wait indefinitely.

        options.artifact_location: (str) Local Decisioning - Fully qualified url to the location
            of the artifact, optional

        options.artifact_payload: (target_decisioning_engine.types.decisioning_artifact.DecisioningArtifact)
            Local Decisioning - A pre-fetched artifact, optional

        options.environment_id: (int) The Target environment ID, defaults to production, optional

        options.environment: (str) The Target environment name, defaults to production, optional

        options.cdn_environment: (str) The CDN environment name, defaults to production, optional

        options.telemetry_enabled: (bool) - If set to false, telemetry data will not be sent to Adobe

        options.version: (str) - The version number of this sdk, optional

        options.property_token: (str) - A property token used to limit the scope of evaluated target
            activities, optional

        options.events: (dict.<str, callable>) An object with event name keys and callback
            function values, optional

        :return TargetClient instance object
        """

        error = validate_client_options(options)

        if error:
            raise Exception(error)

        opts = dict(DEFAULT_OPTS)
        opts.update(options)
        client = TargetClient(opts)
        client.initialize()
        return client

    def get_offers(self, options):
        """Fetches personalization offers
        :param options: (dict) Request options

        options.request: (delivery_api_client.Model.delivery_request.DeliveryRequest)
            Target View Delivery API request, required

        options.target_cookie: (str) Target cookie, optional

        options.target_location_hint: (str) Target Location Hint, optional

        options.consumer_id: (str) When stitching multiple calls, different consumerIds should be provided, optional

        options.customer_ids: (list) A list of Customer Ids in VisitorId-compatible format, optional

        options.session_id: (str) Session Id, used for linking multiple requests, optional

        options.decisioning_method: ("on-device"|"server-side"|"hybrid") Execution mode, defaults to remote, optional

        options.callback: (callable) If handling request asynchronously, the callback is invoked when response is ready

        options.err_callback: (callable) If handling request asynchronously, error callback is invoked when exception
            is raised
        :return (target_python_sdk.types.target_delivery_response.TargetDeliveryResponse)
            Returns response synchronously if no options.callback provided, otherwise returns AsyncResult.
            If callback was provided then a DeliveryResponse will be returned through that.
        """

        error = validate_get_offers_options(options)

        if error:
            raise Exception(error)

        config = deepcopy(self.config)
        config["decisioning_method"] = options.get(
            "decisioning_method") or self.config.get("decisioning_method")
        target_options = {"config": config}
        target_options.update(options)
        return execute_delivery(self.config, target_options,
                                self.decisioning_engine)

    def send_notifications(self, options):
        """ The TargetClient sendNotifications method
        :param options: (dict) Notifications request options

        options.request: (delivery_api_client.Model.delivery_request.DeliveryRequest)
            Target View Delivery API request, required

        options.target_cookie: (str) Target cookie, optional

        options.target_location_hint: (str) Target Location Hint, optional

        options.consumer_id: (str) When stitching multiple calls, different consumerIds should be provided, optional

        options.customer_ids: (list) A list of Customer Ids in VisitorId-compatible format, optional

        options.session_id: (str) Session Id, used for linking multiple requests, optional

        options.callback: (callable) If handling request asynchronously, the callback is invoked when response is ready

        options.err_callback: (callable) If handling request asynchronously, error callback is invoked when exception
            is raised

        :return (target_python_sdk.types.target_delivery_response.TargetDeliveryResponse)
            Returns response synchronously if no options.callback provided, otherwise returns AsyncResult.
            If callback was provided, then a DeliveryResponse will be returned through that.
        """

        error = validate_send_notifications_options(options)

        if error:
            raise Exception(error)

        config = deepcopy(self.config)
        # execution mode for sending notifications must always be remote
        config["decisioning_method"] = DecisioningMethod.SERVER_SIDE.value
        target_options = {"config": config}
        target_options.update(options)
        return execute_delivery(self.config, target_options)

    def get_attributes(self, mbox_names, options=None):
        """
        The TargetClient get_attributes method
        :param mbox_names: (list) A list of mbox names that contains JSON content attributes, required
        :param options: (dict) Request options
        options.request: (delivery_api_client.Model.delivery_request.DeliveryRequest)
            Target View Delivery API request, required
        options.target_cookie: (str) Target cookie, optional
        options.target_location_hint: (str) Target Location Hint, optional
        options.consumer_id: (str) When stitching multiple calls, different consumerIds should be provided, optional
        options.customer_ids: (list) A list of Customer Ids in VisitorId-compatible format, optional
        options.session_id: (str) Session Id, used for linking multiple requests, optional
        options.decisioning_method: ("on-device"|"server-side"|"hybrid") Execution mode, defaults to remote, optional
        options.callback: (callable) If handling request asynchronously, the callback is invoked when response is ready
        options.err_callback: (callable) If handling request asynchronously, error callback is invoked when exception
            is raised
        :return (target_tools.attributes_provider.AttributesProvider|AsyncResult)
            Returns AttributesProvider synchronously if no options.callback provided, otherwise returns AsyncResult.
            If callback was provided then an AttributesProvider will be returned through that
        """

        if not options or not options.get("request"):
            options = {"request": EMPTY_REQUEST}

        add_mboxes_to_request(mbox_names, options.get("request"), "execute")

        if options.get("callback"):
            wrapped_callback = compose_functions(options.get("callback"),
                                                 get_attributes_callback)
            options["callback"] = wrapped_callback
            return self.get_offers(options)

        response = self.get_offers(options)
        return AttributesProvider(response)
Пример #8
0
class TestTargetDecisioningEngine(unittest.TestCase):
    def setUp(self):
        self.decisioning = None

    def tearDown(self):
        if self.decisioning:
            self.decisioning.stop_polling()

    def test_initialize(self):
        config = deepcopy(CONFIG)
        config.polling_interval = 0
        self.decisioning = TargetDecisioningEngine(config)

        with patch.object(PoolManager,
                          "request",
                          return_value=MOCK_ARTIFACT_RESPONSE):
            self.decisioning.initialize()

        self.assertEqual(self.decisioning.artifact, ARTIFACT_BLANK)
        self.assertIsNotNone(self.decisioning._artifact_provider)
        self.assertEqual(
            self.decisioning._artifact_provider.subscription_count, 2)

    def test_initialize_updates_artifact_on_polling_interval(self):
        config = deepcopy(CONFIG)
        config.polling_interval = 1  # seconds
        self.decisioning = TargetDecisioningEngine(config)

        with patch(
                "target_decisioning_engine.artifact_provider.get_min_polling_interval",
                return_value=0):
            with patch.object(PoolManager,
                              "request",
                              return_value=MOCK_ARTIFACT_RESPONSE
                              ) as artifact_request_mock:
                self.decisioning.initialize()

                time.sleep(5)

                self.assertGreaterEqual(artifact_request_mock.call_count, 4)
                artifact = self.decisioning.get_raw_artifact()
                self.assertIsNotNone(artifact)

    def test_initialize_error_fetching_artifact(self):
        config = deepcopy(CONFIG)
        config.polling_interval = 0
        config.event_emitter = Mock()
        self.decisioning = TargetDecisioningEngine(config)

        with patch.object(PoolManager,
                          "request",
                          return_value=MOCK_ARTIFACT_RESPONSE_BAD):
            with patch(
                    "target_decisioning_engine.artifact_provider.BACKOFF_FACTOR",
                    0):
                with self.assertRaises(Exception) as err:
                    self.decisioning.initialize()
                    self.assertEqual(
                        json.loads(err.exception.body), {
                            "status": 500,
                            "message":
                            "The decisioning artifact is not available"
                        })
                    self.assertEqual(config.event_emitter.call_count,
                                     11)  # validate retries
                    self.assertEqual(config.event_emitter.call_args[0][0],
                                     ARTIFACT_DOWNLOAD_FAILED)

    def test_get_offers(self):
        config = deepcopy(CONFIG)
        config.polling_interval = 0
        self.decisioning = TargetDecisioningEngine(config)

        with patch.object(PoolManager,
                          "request",
                          return_value=MOCK_ARTIFACT_RESPONSE):
            self.decisioning.initialize()

        get_offers_opts = TargetDeliveryRequest(request=TARGET_REQUEST,
                                                session_id="dummy_session")
        offers = self.decisioning.get_offers(get_offers_opts)
        self.assertIsNotNone(offers)

    def test_get_offers_unsupported_artifact_version(self):
        config = deepcopy(CONFIG)
        config.polling_interval = 0
        self.decisioning = TargetDecisioningEngine(config)

        with patch.object(
                PoolManager,
                "request",
                return_value=MOCK_ARTIFACT_RESPONSE_UNSUPPORTED_VERSION):
            self.decisioning.initialize()

        with self.assertRaises(Exception) as err:
            get_offers_opts = TargetDeliveryRequest(request=TARGET_REQUEST,
                                                    session_id="dummy_session")
            self.decisioning.get_offers(get_offers_opts)
            self.assertEqual(
                json.loads(err.exception.body),
                MESSAGES.get("ARTIFACT_VERSION_UNSUPPORTED")(
                    ARTIFACT_UNSUPPORTED_VERSION.get("version"),
                    SUPPORTED_ARTIFACT_MAJOR_VERSION))