Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions switcher_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,14 @@ def clear_resources() -> None:
ExecutionLogger.clear_logger()
GlobalSnapshot.clear()
TimedMatch.terminate_worker()

@staticmethod
def subscribe_notify_error(callback: Callable[[Exception], None]) -> None:
"""
Subscribe to notify when an asynchronous error is thrown.
It is usually used when throttle and silent mode are enabled.
"""
ExecutionLogger.subscribe_notify_error(callback)

@staticmethod
def _is_check_snapshot_available(fetch_remote = False) -> bool:
Expand Down
5 changes: 5 additions & 0 deletions switcher_client/lib/utils/execution_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ def clear_logger() -> None:
"""Clear all results"""
global _logger
_logger.clear()

@staticmethod
def subscribe_notify_error(callback: Callable[[Exception], None]) -> None:
"""Subscribe to notify when an asynchronous error is thrown."""
ExecutionLogger._callback_error = callback

@staticmethod
def _has_execution(log: 'ExecutionLogger', key: str, input: Optional[List[List[str]]]) -> bool:
Expand Down
7 changes: 7 additions & 0 deletions switcher_client/switcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ def _submit(self) -> ResultDetail:

return self._execute_remote_criteria()
except Exception as e:
self._notify_error(e)

if self._context.options.silent_mode:
RemoteAuth.update_silent_token()
return self._execute_local_criteria()
Expand Down Expand Up @@ -81,6 +83,11 @@ def _execute_api_checks(self):
if RemoteAuth.is_token_expired():
self.prepare(self._key)

def _notify_error(self, error: Exception):
""" Notify asynchronous error to the subscribed callback """
if ExecutionLogger._callback_error:
ExecutionLogger._callback_error(error)

def _execute_remote_criteria(self):
""" Execute remote criteria """
token = GlobalAuth.get_token()
Expand Down
12 changes: 11 additions & 1 deletion tests/test_switcher_silent_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from switcher_client import Client
from switcher_client.lib.globals.global_context import ContextOptions

async_error = None

def test_silent_mode_for_check_criteria(httpx_mock):
""" Should use the silent mode when the remote API is not available for check criteria """

Expand All @@ -15,15 +17,19 @@ def test_silent_mode_for_check_criteria(httpx_mock):
given_check_health(httpx_mock, status=500)
given_context(silent_mode='1s')

Client.subscribe_notify_error(lambda error: globals().update(async_error=str(error)))
switcher = Client.get_switcher('FF2FOR2022')

# test
# assert silent mode being used while registering the error
assert switcher.is_on('FF2FOR2022')
assert async_error == '[check_criteria] failed with status: 429'

# assert silent mode being used in the next call
time.sleep(1.5)
globals().update(async_error=None)
assert switcher.is_on('FF2FOR2022')
assert async_error is None

def test_silent_mode_for_check_criteria_restabilished(httpx_mock):
""" Should retry check criteria once the remote API is restabilished and the token is renewed """
Expand All @@ -33,17 +39,22 @@ def test_silent_mode_for_check_criteria_restabilished(httpx_mock):
given_check_criteria(httpx_mock, key='FF2FOR2022', response={'error': 'Too many requests'}, status=429)
given_context(silent_mode='1s')

Client.subscribe_notify_error(lambda error: globals().update(async_error=str(error)))
switcher = Client.get_switcher('FF2FOR2022')

# test
# assert silent mode being used while registering the error
assert switcher.is_on('FF2FOR2022')
assert async_error == '[check_criteria] failed with status: 429'

# assert from remote API once the API is restabilished and the token is renewed
given_check_criteria(httpx_mock, key='FF2FOR2022', response={'result': True}, status=200)
given_check_health(httpx_mock, status=200)

time.sleep(1.5)
globals().update(async_error=None)
assert switcher.is_on('FF2FOR2022')
assert async_error is None

# Helpers

Expand Down Expand Up @@ -85,4 +96,3 @@ def given_check_health(httpx_mock: HTTPXMock, status=200):
method='GET',
status_code=status,
)