From 763ab5093d89e4a7fe0234c795f67f1eed04b9a2 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Thu, 16 Sep 2021 13:41:25 +0530 Subject: [PATCH 01/12] feat(flag-analytics): Add module to suppport flag analytics for more: https://docs.flagsmith.com/advanced-use/flag-analytics --- flagsmith/analytics.py | 50 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 flagsmith/analytics.py diff --git a/flagsmith/analytics.py b/flagsmith/analytics.py new file mode 100644 index 0000000..80fb480 --- /dev/null +++ b/flagsmith/analytics.py @@ -0,0 +1,50 @@ +import logging +from collections import UserDict +from datetime import datetime + +import requests + +logger = logging.getLogger(__name__) + +ANALYTICS_ENDPOINT = "analytics/flags/" +ANALYTICS_TIMER = 10 + + +class AnalyticsProc(UserDict): + """ + AnalyticsProc is you to track how often individual Flags are evaluated within the Flagsmith SDK + docs: https://docs.flagsmith.com/advanced-use/flag-analytics + """ + + def __init__(self, environment_id, api): + """ + Initialise Flagsmith environment. + + :param environment_id: environment key obtained from the Flagsmith UI + :param api: api url to override when using self hosted version + """ + self.analytics_endpoint = api + ANALYTICS_ENDPOINT + self.environment_id = environment_id + self._last_flushed = datetime.now() + super().__init__() + + def __setitem__(self, key, value): + super().__setitem__(key, value) + if (datetime.now() - self._last_flushed).seconds > ANALYTICS_TIMER: + self.flush() + self._last_flushed = datetime.now() + self.clear() + + def flush(self): + try: + requests.post( + self.analytics_endpoint, + data=self.data, + timeout=5, + headers={"X-Environment-Key": self.environment_id}, + ) + except Exception as e: + logger.error("Failed to send anaytics data. Error message was %s" % e) + + def track_feature(self, feature_id: int): + self[feature_id] = self.get(feature_id, 0) + 1 From dbadf6a4c28bb116c8e6be621dbcd9a92d507a57 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Thu, 16 Sep 2021 13:42:58 +0530 Subject: [PATCH 02/12] feat(analytics-flag): Add analytics-flag to python sdk --- flagsmith/flagsmith.py | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index 03b1c4d..046e66a 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -2,6 +2,8 @@ import requests +from .analytics import AnalyticsProc + logger = logging.getLogger(__name__) SERVER_URL = "https://api.flagsmith.com/api/v1/" @@ -19,12 +21,14 @@ def __init__(self, environment_id, api=SERVER_URL, request_timeout=None): :param api: (optional) api url to override when using self hosted version :param request_timeout: (optional) request timeout in seconds """ + self.environment_id = environment_id self.api = api self.flags_endpoint = api + FLAGS_ENDPOINT self.identities_endpoint = api + IDENTITY_ENDPOINT self.traits_endpoint = api + TRAIT_ENDPOINT self.request_timeout = request_timeout + self._analytics_proc = AnalyticsProc(environment_id, api) def get_flags(self, identity=None): """ @@ -62,8 +66,9 @@ def has_feature(self, feature_name): :return: True if exists, False if not. """ data = self._get_flags_response(feature_name) - if data: + feature_id = data["feature"]["id"] + self._analytics_proc.track_feature(feature_id) return True return False @@ -72,7 +77,7 @@ def feature_enabled(self, feature_name, identity=None): """ Get enabled state of given feature for an environment. - :param feature_name: name of feature to determine if enabled (must match 'ID' on flagsmith.com) + :param feature_name: name of feature to determine if enabled :param identity: (optional) application's unique identifier for the user to check feature state :return: True / False if feature exists. None otherwise. """ @@ -81,21 +86,19 @@ def feature_enabled(self, feature_name, identity=None): data = self._get_flags_response(feature_name, identity) - if data: - if data.get("flags"): - for flag in data.get("flags"): - if flag["feature"]["name"] == feature_name: - return flag["enabled"] - else: - return data["enabled"] - else: + if not data: return None + feature_id = data["feature"]["id"] + self._analytics_proc.track_feature(feature_id) + + return data["enabled"] + def get_value(self, feature_name, identity=None): """ Get value of given feature for an environment. - :param feature_name: name of feature to determine value of (must match 'ID' on flagsmith.com) + :param feature_name: name of feature to determine value of :param identity: (optional) application's unique identifier for the user to check feature state :return: value of the feature state if feature exists, None otherwise """ @@ -104,17 +107,11 @@ def get_value(self, feature_name, identity=None): data = self._get_flags_response(feature_name, identity) - if data: - # using new endpoints means that the flags come back in a list, filter this for the one we want and - # return it's value - if data.get("flags"): - for flag in data.get("flags"): - if flag["feature"]["name"] == feature_name: - return flag["feature_state_value"] - else: - return data["feature_state_value"] - else: + if not data: return None + feature_id = data["feature"]["id"] + self._analytics_proc.track_feature(feature_id) + return data["feature_state_value"] def get_trait(self, trait_key, identity): """ From 7afa58ba87d25201a1a6fa5c0dab90af814cc9d6 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Mon, 20 Sep 2021 09:21:55 +0530 Subject: [PATCH 03/12] refact(flag-analytics): Make analytics data async --- .isort.cfg | 2 +- flagsmith/analytics.py | 11 ++++++++--- requirements.txt | 3 ++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.isort.cfg b/.isort.cfg index 432a536..e1acc55 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -3,4 +3,4 @@ use_parentheses=true multi_line_output=3 include_trailing_comma=true line_length=79 -known_third_party = requests,setuptools +known_third_party = requests,requests_futures,setuptools diff --git a/flagsmith/analytics.py b/flagsmith/analytics.py index 80fb480..3dd905e 100644 --- a/flagsmith/analytics.py +++ b/flagsmith/analytics.py @@ -2,17 +2,21 @@ from collections import UserDict from datetime import datetime -import requests +from requests_futures.sessions import FuturesSession logger = logging.getLogger(__name__) ANALYTICS_ENDPOINT = "analytics/flags/" + +# Used to control how often we send data(in seconds) ANALYTICS_TIMER = 10 +session = FuturesSession() + class AnalyticsProc(UserDict): """ - AnalyticsProc is you to track how often individual Flags are evaluated within the Flagsmith SDK + AnalyticsProc is usedd to track how often individual Flags are evaluated within the Flagsmith SDK docs: https://docs.flagsmith.com/advanced-use/flag-analytics """ @@ -37,12 +41,13 @@ def __setitem__(self, key, value): def flush(self): try: - requests.post( + session.post( self.analytics_endpoint, data=self.data, timeout=5, headers={"X-Environment-Key": self.environment_id}, ) + except Exception as e: logger.error("Failed to send anaytics data. Error message was %s" % e) diff --git a/requirements.txt b/requirements.txt index 247e33f..abffd05 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -requests>=2.19.1 \ No newline at end of file +requests>=2.19.1 +requests-futures==1.0.0 From 8c221eb7ef396f9f1664596acee233338af9c256 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Mon, 20 Sep 2021 21:15:32 +0530 Subject: [PATCH 04/12] fix typo --- flagsmith/analytics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flagsmith/analytics.py b/flagsmith/analytics.py index 3dd905e..694afaa 100644 --- a/flagsmith/analytics.py +++ b/flagsmith/analytics.py @@ -16,7 +16,7 @@ class AnalyticsProc(UserDict): """ - AnalyticsProc is usedd to track how often individual Flags are evaluated within the Flagsmith SDK + AnalyticsProc is used to track how often individual Flags are evaluated within the Flagsmith SDK docs: https://docs.flagsmith.com/advanced-use/flag-analytics """ From 98b81598a71d959fbce52aa6f84aa60c8f2dda31 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Tue, 21 Sep 2021 19:32:17 +0530 Subject: [PATCH 05/12] squash! refac(analytics) --- flagsmith/analytics.py | 30 +++++++++++++++--------------- flagsmith/flagsmith.py | 8 ++++---- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/flagsmith/analytics.py b/flagsmith/analytics.py index 694afaa..c47c9d8 100644 --- a/flagsmith/analytics.py +++ b/flagsmith/analytics.py @@ -14,42 +14,42 @@ session = FuturesSession() -class AnalyticsProc(UserDict): +class AnalyticsProcessor: """ - AnalyticsProc is used to track how often individual Flags are evaluated within the Flagsmith SDK + AnalyticsProcessor is used to track how often individual Flags are evaluated within the Flagsmith SDK docs: https://docs.flagsmith.com/advanced-use/flag-analytics """ - def __init__(self, environment_id, api): + def __init__(self, environment_key: str, base_api_url: str): """ Initialise Flagsmith environment. :param environment_id: environment key obtained from the Flagsmith UI :param api: api url to override when using self hosted version """ - self.analytics_endpoint = api + ANALYTICS_ENDPOINT - self.environment_id = environment_id + self.analytics_endpoint = base_api_url + ANALYTICS_ENDPOINT + self.environment_key = environment_key self._last_flushed = datetime.now() + self.analytics_data = {} super().__init__() - def __setitem__(self, key, value): - super().__setitem__(key, value) - if (datetime.now() - self._last_flushed).seconds > ANALYTICS_TIMER: - self.flush() - self._last_flushed = datetime.now() - self.clear() - def flush(self): + # Sends all the collected data to the api asynchronously and resets the timer try: session.post( self.analytics_endpoint, - data=self.data, + data=self.analytics_data, timeout=5, - headers={"X-Environment-Key": self.environment_id}, + headers={"X-Environment-Key": self.environment_key}, ) + self.analytics_data.clear() + self._last_flushed = datetime.now() + except Exception as e: logger.error("Failed to send anaytics data. Error message was %s" % e) def track_feature(self, feature_id: int): - self[feature_id] = self.get(feature_id, 0) + 1 + self.analytics_data[feature_id] = self.analytics_data.get(feature_id, 0) + 1 + if (datetime.now() - self._last_flushed).seconds > ANALYTICS_TIMER: + self.flush() diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index 046e66a..e05b1da 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -2,7 +2,7 @@ import requests -from .analytics import AnalyticsProc +from .analytics import AnalyticsProcessor logger = logging.getLogger(__name__) @@ -68,7 +68,7 @@ def has_feature(self, feature_name): data = self._get_flags_response(feature_name) if data: feature_id = data["feature"]["id"] - self._analytics_proc.track_feature(feature_id) + self._analytics_processor.track_feature(feature_id) return True return False @@ -90,7 +90,7 @@ def feature_enabled(self, feature_name, identity=None): return None feature_id = data["feature"]["id"] - self._analytics_proc.track_feature(feature_id) + self._analytics_processor.track_feature(feature_id) return data["enabled"] @@ -110,7 +110,7 @@ def get_value(self, feature_name, identity=None): if not data: return None feature_id = data["feature"]["id"] - self._analytics_proc.track_feature(feature_id) + self._analytics_processor.track_feature(feature_id) return data["feature_state_value"] def get_trait(self, trait_key, identity): From 07a6bfdaf3106f942a8f970b15cc17286a13782d Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Mon, 4 Oct 2021 12:46:16 +0530 Subject: [PATCH 06/12] minor refactoring and docs --- flagsmith/analytics.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/flagsmith/analytics.py b/flagsmith/analytics.py index c47c9d8..3418ed8 100644 --- a/flagsmith/analytics.py +++ b/flagsmith/analytics.py @@ -1,7 +1,9 @@ +import copy import logging from collections import UserDict from datetime import datetime +import requests from requests_futures.sessions import FuturesSession logger = logging.getLogger(__name__) @@ -11,7 +13,7 @@ # Used to control how often we send data(in seconds) ANALYTICS_TIMER = 10 -session = FuturesSession() +session = FuturesSession(max_workers=4) class AnalyticsProcessor: @@ -20,33 +22,43 @@ class AnalyticsProcessor: docs: https://docs.flagsmith.com/advanced-use/flag-analytics """ - def __init__(self, environment_key: str, base_api_url: str): + def __init__(self, environment_key: str, base_api_url: str, timeout: int = 3): """ Initialise Flagsmith environment. - :param environment_id: environment key obtained from the Flagsmith UI - :param api: api url to override when using self hosted version + :param environment_key: environment key obtained from the Flagsmith UI + :param base_api_url: base api url to override when using self hosted version + :param timeout: used to tell requests to stop waiting for a response after a given number of seconds """ self.analytics_endpoint = base_api_url + ANALYTICS_ENDPOINT self.environment_key = environment_key self._last_flushed = datetime.now() self.analytics_data = {} + self.timeout = timeout super().__init__() def flush(self): - # Sends all the collected data to the api asynchronously and resets the timer + """ + Sends all the collected data to the api asynchronously and resets the timer + """ + + if not self.analytics_data: + return try: + # create a copy of the `analytics_data` because we call `clear` on the dictionary later + # and for the obvious reason when mocking we get the empty dict. + data = copy.copy(self.analytics_data) session.post( self.analytics_endpoint, - data=self.analytics_data, - timeout=5, + data=data, + timeout=self.timeout, headers={"X-Environment-Key": self.environment_key}, ) self.analytics_data.clear() self._last_flushed = datetime.now() - except Exception as e: + except requests.RequestException as e: logger.error("Failed to send anaytics data. Error message was %s" % e) def track_feature(self, feature_id: int): From f36ae71452af86e8f71f4d4f3ae9e731d2ab9ced Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Mon, 4 Oct 2021 12:47:04 +0530 Subject: [PATCH 07/12] Add test module for analytics --- .isort.cfg | 2 +- tests/conftest.py | 10 +++++++ tests/test_analytics.py | 60 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 tests/conftest.py create mode 100644 tests/test_analytics.py diff --git a/.isort.cfg b/.isort.cfg index e1acc55..12a52aa 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -3,4 +3,4 @@ use_parentheses=true multi_line_output=3 include_trailing_comma=true line_length=79 -known_third_party = requests,requests_futures,setuptools +known_third_party = pytest,requests,requests_futures,setuptools diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..7f0cc1c --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,10 @@ +import pytest + +from flagsmith.analytics import AnalyticsProcessor + + +@pytest.fixture +def analytics_processor(): + return AnalyticsProcessor( + environment_key="test_key", base_api_url="http://test_url" + ) diff --git a/tests/test_analytics.py b/tests/test_analytics.py new file mode 100644 index 0000000..4b8278d --- /dev/null +++ b/tests/test_analytics.py @@ -0,0 +1,60 @@ +from datetime import datetime, timedelta +from unittest import mock + +from flagsmith.analytics import ANALYTICS_TIMER, AnalyticsProcessor + + +def test_analytics_processor_track_feature_updates_analytics_data(analytics_processor): + # When + analytics_processor.track_feature(1) + assert analytics_processor.analytics_data[1] == 1 + + analytics_processor.track_feature(1) + assert analytics_processor.analytics_data[1] == 2 + + +def test_analytics_processor_flush_clears_analytics_data(analytics_processor): + analytics_processor.track_feature(1) + analytics_processor.flush() + assert analytics_processor.analytics_data == {} + + +def test_analytics_processor_flush_post_request_data_match_ananlytics_data( + analytics_processor, +): + # Given + with mock.patch("flagsmith.analytics.session") as session: + # When + analytics_processor.track_feature(1) + analytics_processor.track_feature(2) + analytics_processor.flush() + # Then + session.post.assert_called() + post_call = session.mock_calls[0] + assert {1: 1, 2: 1} == post_call.kwargs["data"] + + +def test_analytics_processor_flush_early_exit_if_analytics_data_is_empty( + analytics_processor, +): + with mock.patch("flagsmith.analytics.session") as session: + analytics_processor.flush() + + # Then + session.post.assert_not_called() + + +def test_analytics_processor_calling_track_feature_calls_flush_when_timer_runs_out( + analytics_processor, +): + # Given + analytics_processor.flush = mock.Mock() + with mock.patch("flagsmith.analytics.datetime") as mocked_datetime: + # Let's move the time + mocked_datetime.now.return_value = datetime.now() + timedelta( + seconds=ANALYTICS_TIMER + 1 + ) + # When + analytics_processor.track_feature(1) + # Then + analytics_processor.flush.assert_called() From 2f43e2f8e9b2340a2113cfed1cceb38ee49bf594 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Mon, 4 Oct 2021 13:04:20 +0530 Subject: [PATCH 08/12] fix: mock call for py3.6-3.7 --- tests/test_analytics.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_analytics.py b/tests/test_analytics.py index 4b8278d..0f231cc 100644 --- a/tests/test_analytics.py +++ b/tests/test_analytics.py @@ -31,7 +31,8 @@ def test_analytics_processor_flush_post_request_data_match_ananlytics_data( # Then session.post.assert_called() post_call = session.mock_calls[0] - assert {1: 1, 2: 1} == post_call.kwargs["data"] + + assert {1: 1, 2: 1} == post_call[2]["data"] def test_analytics_processor_flush_early_exit_if_analytics_data_is_empty( From 5345ede16082acb7a10a111c6f5a6b14d99f8c99 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Tue, 5 Oct 2021 17:52:46 +0530 Subject: [PATCH 09/12] refactor: remove data copy and other minor changes --- flagsmith/analytics.py | 35 +++++++++++++---------------------- tests/test_analytics.py | 12 +++++++----- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/flagsmith/analytics.py b/flagsmith/analytics.py index 3418ed8..c344d26 100644 --- a/flagsmith/analytics.py +++ b/flagsmith/analytics.py @@ -1,13 +1,8 @@ -import copy -import logging -from collections import UserDict +import json from datetime import datetime -import requests from requests_futures.sessions import FuturesSession -logger = logging.getLogger(__name__) - ANALYTICS_ENDPOINT = "analytics/flags/" # Used to control how often we send data(in seconds) @@ -44,22 +39,18 @@ def flush(self): if not self.analytics_data: return - try: - # create a copy of the `analytics_data` because we call `clear` on the dictionary later - # and for the obvious reason when mocking we get the empty dict. - data = copy.copy(self.analytics_data) - session.post( - self.analytics_endpoint, - data=data, - timeout=self.timeout, - headers={"X-Environment-Key": self.environment_key}, - ) - - self.analytics_data.clear() - self._last_flushed = datetime.now() - - except requests.RequestException as e: - logger.error("Failed to send anaytics data. Error message was %s" % e) + session.post( + self.analytics_endpoint, + data=json.dumps(self.analytics_data), + timeout=self.timeout, + headers={ + "X-Environment-Key": self.environment_key, + "Content-type": "application/json", + }, + ) + + self.analytics_data.clear() + self._last_flushed = datetime.now() def track_feature(self, feature_id: int): self.analytics_data[feature_id] = self.analytics_data.get(feature_id, 0) + 1 diff --git a/tests/test_analytics.py b/tests/test_analytics.py index 0f231cc..d6e70ea 100644 --- a/tests/test_analytics.py +++ b/tests/test_analytics.py @@ -1,3 +1,4 @@ +import json from datetime import datetime, timedelta from unittest import mock @@ -31,8 +32,7 @@ def test_analytics_processor_flush_post_request_data_match_ananlytics_data( # Then session.post.assert_called() post_call = session.mock_calls[0] - - assert {1: 1, 2: 1} == post_call[2]["data"] + assert {"1": 1, "2": 1} == json.loads(post_call[2]["data"]) def test_analytics_processor_flush_early_exit_if_analytics_data_is_empty( @@ -49,13 +49,15 @@ def test_analytics_processor_calling_track_feature_calls_flush_when_timer_runs_o analytics_processor, ): # Given - analytics_processor.flush = mock.Mock() - with mock.patch("flagsmith.analytics.datetime") as mocked_datetime: + with mock.patch("flagsmith.analytics.datetime") as mocked_datetime, mock.patch( + "flagsmith.analytics.session" + ) as session: # Let's move the time mocked_datetime.now.return_value = datetime.now() + timedelta( seconds=ANALYTICS_TIMER + 1 ) # When analytics_processor.track_feature(1) + # Then - analytics_processor.flush.assert_called() + session.post.assert_called() From 2ddde6a56363e872db59b2c2c171e02738d381e3 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Tue, 5 Oct 2021 18:25:59 +0100 Subject: [PATCH 10/12] Fix casing of Content-Type header --- flagsmith/analytics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flagsmith/analytics.py b/flagsmith/analytics.py index c344d26..cefe447 100644 --- a/flagsmith/analytics.py +++ b/flagsmith/analytics.py @@ -45,7 +45,7 @@ def flush(self): timeout=self.timeout, headers={ "X-Environment-Key": self.environment_key, - "Content-type": "application/json", + "Content-Type": "application/json", }, ) From 52efd24754f80078fe536246fd79e5b980dc2595 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Tue, 5 Oct 2021 18:27:40 +0100 Subject: [PATCH 11/12] Tidy up docstrings --- flagsmith/analytics.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/flagsmith/analytics.py b/flagsmith/analytics.py index cefe447..5003699 100644 --- a/flagsmith/analytics.py +++ b/flagsmith/analytics.py @@ -13,17 +13,19 @@ class AnalyticsProcessor: """ - AnalyticsProcessor is used to track how often individual Flags are evaluated within the Flagsmith SDK - docs: https://docs.flagsmith.com/advanced-use/flag-analytics + AnalyticsProcessor is used to track how often individual Flags are evaluated within + the Flagsmith SDK. Docs: https://docs.flagsmith.com/advanced-use/flag-analytics. """ def __init__(self, environment_key: str, base_api_url: str, timeout: int = 3): """ - Initialise Flagsmith environment. + Initialise the AnalyticsProcessor to handle sending analytics on flag usage to + the Flagsmith API. :param environment_key: environment key obtained from the Flagsmith UI - :param base_api_url: base api url to override when using self hosted version - :param timeout: used to tell requests to stop waiting for a response after a given number of seconds + :param base_api_url: base api url to override when using self hosted version + :param timeout: used to tell requests to stop waiting for a response after a + given number of seconds """ self.analytics_endpoint = base_api_url + ANALYTICS_ENDPOINT self.environment_key = environment_key From dcbe95e4fe8c5f06626fb6df71d8da1cf36a5dd1 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Wed, 6 Oct 2021 08:59:04 +0530 Subject: [PATCH 12/12] fixup! add timeout --- flagsmith/flagsmith.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index e05b1da..b8ad229 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -28,7 +28,9 @@ def __init__(self, environment_id, api=SERVER_URL, request_timeout=None): self.identities_endpoint = api + IDENTITY_ENDPOINT self.traits_endpoint = api + TRAIT_ENDPOINT self.request_timeout = request_timeout - self._analytics_proc = AnalyticsProc(environment_id, api) + self._analytics_processor = AnalyticsProcessor( + environment_id, api, self.request_timeout + ) def get_flags(self, identity=None): """