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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ Client.build_context(
snapshot_location='./snapshot/',
snapshot_auto_update_interval=3,
silent_mode='5m',
restrict_relay=True,
throttle_max_workers=2,
regex_max_black_list=10,
regex_max_time_limit=100,
Expand All @@ -154,6 +155,7 @@ switcher = Client.get_switcher()
| `snapshot_location` | `str` | Directory for snapshot files | `'./snapshot/'` |
| `snapshot_auto_update_interval` | `int` | Auto-update interval in seconds (0 = disabled) | `0` |
| `silent_mode` | `str` | Silent mode retry time (e.g., '5m' for 5 minutes) | `None` |
| `restrict_relay` | `bool` | 🚧 TODO - Enable relay restrictions in local mode | `True` |
| `throttle_max_workers` | `int` | Max workers for throttling feature checks | `None` |
| `regex_max_black_list` | `int` | Max cached entries for failed regex | `100` |
| `regex_max_time_limit` | `int` | Regex execution time limit (ms) | `3000` |
Expand Down
3 changes: 2 additions & 1 deletion switcher_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ def get_switcher(key: Optional[str] = None) -> Switcher:
if persisted_switcher is not None:
return persisted_switcher

switcher = Switcher(Client._context, key_value)
switcher = Switcher(Client._context, key_value) \
.restrict_relay(Client._context.options.restrict_relay)

if key_value != '':
Client._switcher[key_value] = switcher
Expand Down
14 changes: 9 additions & 5 deletions switcher_client/lib/globals/global_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
DEFAULT_LOCAL = False
DEFAULT_LOGGER = False
DEFAULT_FREEZE = False
DEFAULT_RESTRICT_RELAY = True
DEFAULT_REGEX_MAX_BLACKLISTED = 100
DEFAULT_REGEX_MAX_TIME_LIMIT = 3000

Expand All @@ -18,14 +19,16 @@ class ContextOptions:
:param throttle_max_workers: The maximum number of workers to use for background refresh when throttle is enabled. If None, the default value is based on the number of CPUs. Default is None
:param regex_max_black_list: The maximum number of blacklisted regex inputs. If not set, it will use the default value of 100
:param regex_max_time_limit: The maximum time limit in milliseconds for regex matching. If not set, it will use the default value of 3000 ms
:param restrict_relay: When enabled it will restrict the use of relay when local is enabled. Default is True
"""

def __init__(self,
local = DEFAULT_LOCAL,
logger = DEFAULT_LOGGER,
freeze = DEFAULT_FREEZE,
regex_max_black_list = DEFAULT_REGEX_MAX_BLACKLISTED,
regex_max_time_limit = DEFAULT_REGEX_MAX_TIME_LIMIT,
local: bool = DEFAULT_LOCAL,
logger: bool = DEFAULT_LOGGER,
freeze: bool = DEFAULT_FREEZE,
regex_max_black_list: int = DEFAULT_REGEX_MAX_BLACKLISTED,
regex_max_time_limit: int = DEFAULT_REGEX_MAX_TIME_LIMIT,
restrict_relay: bool = DEFAULT_RESTRICT_RELAY,
snapshot_location: Optional[str] = None,
snapshot_auto_update_interval: Optional[int] = None,
silent_mode: Optional[str] = None,
Expand All @@ -36,6 +39,7 @@ def __init__(self,
self.snapshot_location = snapshot_location
self.snapshot_auto_update_interval = snapshot_auto_update_interval
self.silent_mode = silent_mode
self.restrict_relay = restrict_relay
self.throttle_max_workers = throttle_max_workers
self.regex_max_black_list = regex_max_black_list
self.regex_max_time_limit = regex_max_time_limit
Expand Down
10 changes: 9 additions & 1 deletion switcher_client/lib/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ def _check_config(config: Config, switcher: SwitcherData) -> ResultDetail:
if config.activated is False:
return ResultDetail.disabled("Config disabled")

if Resolver._has_relay_enabled(config) and switcher._restrict_relay:
return ResultDetail.disabled("Config has relay enabled")

if config.strategies is not None and len(config.strategies) > 0:
return Resolver._check_strategy(config.strategies, switcher._input)

Expand Down Expand Up @@ -81,4 +84,9 @@ def _check_strategy_config(strategy_config: StrategyConfig, entry: list[Entry])
@staticmethod
def _is_strategy_fulfilled(strategy_entry: list[Entry], strategy_config: StrategyConfig) -> bool:
""" Determines if the strategy conditions are fulfilled based on the entries and configuration. """
return len(strategy_entry) > 0 and process_operation(strategy_config, strategy_entry[0].input) is True
return len(strategy_entry) > 0 and process_operation(strategy_config, strategy_entry[0].input) is True

@staticmethod
def _has_relay_enabled(config: Config) -> bool:
""" Checks if the config has relay enabled. """
return config.relay.activated if config.relay else False
6 changes: 6 additions & 0 deletions switcher_client/switcher_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def __init__(self, context: Context,key: Optional[str] = None):
self._next_refresh_time = 0 # timestamp
self._default_result = None
self._force_remote = False
self._restrict_relay = True

def check(self, strategy_type: str, input: str)-> Self:
""" Adds a strategy for validation """
Expand Down Expand Up @@ -67,4 +68,9 @@ def remote(self, force_remote: bool = True) -> Self:
def default_result(self, result: bool) -> Self:
""" Sets the default result for the switcher """
self._default_result = result
return self

def restrict_relay(self, restrict: bool = True) -> Self:
""" Allow local snapshots to ignore or require Relay verification """
self._restrict_relay = restrict
return self
36 changes: 33 additions & 3 deletions tests/test_switcher_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from switcher_client.client import Client, ContextOptions
from switcher_client.errors import LocalCriteriaError
from switcher_client.lib.utils.timed_match.timed_match import DEFAULT_REGEX_MAX_BLACKLISTED, DEFAULT_REGEX_MAX_TIME_LIMIT
from switcher_client.lib.globals.global_context import DEFAULT_REGEX_MAX_BLACKLISTED, DEFAULT_REGEX_MAX_TIME_LIMIT, DEFAULT_RESTRICT_RELAY

async_error = None

Expand Down Expand Up @@ -222,11 +222,40 @@ def test_local_no_snapshot():
except LocalCriteriaError as e:
assert str(e) == "Snapshot not loaded. Try to use 'Client.load_snapshot()'"

def test_local_retrict_relay():
""" Should return disabled when Relay is enabled (using default restrict_relay behavior) """

# given
given_context('tests/snapshots')
snapshot_version = Client.load_snapshot()

switcher = Client.get_switcher()

# test
assert snapshot_version == 1
assert switcher.is_on('USECASE103') is False # relay enabled
assert switcher.restrict_relay(False).is_on('USECASE103') is True # relay enabled (force override)
assert switcher.is_on('USECASE104') is True # relay disabled

def test_local_restrict_relay_override():
""" Should return enabled when Relay is enabled but restrict_relay is overridden to False """

# given
given_context('tests/snapshots', restrict_relay=False)
snapshot_version = Client.load_snapshot()

switcher = Client.get_switcher()

# test
assert snapshot_version == 1
assert switcher.is_on('USECASE103')

# Helpers

def given_context(snapshot_location: str, environment: str = 'default',
regex_max_black_list = DEFAULT_REGEX_MAX_BLACKLISTED,
regex_max_time_limit = DEFAULT_REGEX_MAX_TIME_LIMIT):
regex_max_time_limit = DEFAULT_REGEX_MAX_TIME_LIMIT,
restrict_relay = DEFAULT_RESTRICT_RELAY):
Client.build_context(
domain='Playground',
environment=environment,
Expand All @@ -235,6 +264,7 @@ def given_context(snapshot_location: str, environment: str = 'default',
logger=True,
snapshot_location=snapshot_location,
regex_max_black_list=regex_max_black_list,
regex_max_time_limit=regex_max_time_limit
regex_max_time_limit=regex_max_time_limit,
restrict_relay=restrict_relay
)
)