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
6 changes: 6 additions & 0 deletions .github/workflows/master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ jobs:
run: |
pip install pipenv
pipenv install --dev

- name: Lint
run: pipenv run python -m pylint switcher_client

- name: Test
run: pipenv run pytest -v --cov=./switcher_client --cov-report xml --cov-config=.coveragerc
Expand Down Expand Up @@ -58,6 +61,9 @@ jobs:
run: |
pip install pipenv
pipenv install --dev

- name: Lint
run: pipenv run python -m pylint switcher_client

- name: Test
run: pipenv run pytest -v
3 changes: 3 additions & 0 deletions .github/workflows/staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ jobs:
run: |
pip install pipenv
pipenv install --dev

- name: Lint
run: pipenv run python -m pylint switcher_client

- name: Test
run: pipenv run pytest -v
4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@
"tests", "-s", "-v"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
"python.testing.pytestEnabled": true,
"editor.renderWhitespace": "trailing",
"files.trimTrailingWhitespace": true
}
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
.PHONY: install test cover
.PHONY: install lint test cover

install:
pipenv install --dev

lint:
pipenv run python -m pylint switcher_client

test:
pytest -v --cov=./switcher_client --cov-report xml --cov-config=.coveragerc

Expand Down
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ name = "pypi"
httpx = {extras = ["http2"], version = "==0.28.1"}

[dev-packages]
pylint = "==4.0.5"
pytest = "==9.0.2"
pytest-cov = "==7.0.0"
pytest-httpx = "==0.36.0"
Expand Down
289 changes: 180 additions & 109 deletions Pipfile.lock

Large diffs are not rendered by default.

19 changes: 18 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,21 @@ include = ["switcher_client*"]
[tool.setuptools.dynamic]
version = { attr = "switcher_client.version.__version__" }
readme = { file = ["README.md"], content-type = "text/markdown" }
dependencies = { file = ["requirements.txt"] }
dependencies = { file = ["requirements.txt"] }

[tool.pylint.main]
source-roots = ["switcher_client"]

[tool.pylint.messages_control]
max-line-length = 120
disable = [
"missing-module-docstring",
"missing-class-docstring",
"missing-function-docstring",
# Relative imports are valid within the installed package; pylint reports
# false positives when invoked from the project root without install.
"relative-beyond-top-level"
]

[tool.pylint.format]
max-line-length = 120
2 changes: 1 addition & 1 deletion switcher_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
'Switcher',
'ContextOptions',
'WatchSnapshotCallback',
]
]
79 changes: 43 additions & 36 deletions switcher_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,40 +11,46 @@
from .lib.utils.execution_logger import ExecutionLogger
from .lib.utils.timed_match.timed_match import TimedMatch
from .lib.utils import get
from .errors import SnapshpotNotFoundError
from .switcher import Switcher

class SwitcherOptions:
REGEX_MAX_BLACK_LIST = 'regex_max_black_list'
REGEX_MAX_TIME_LIMIT = 'regex_max_time_limit'
SNAPSHOT_AUTO_UPDATE_INTERVAL = 'snapshot_auto_update_interval'
SILENT_MODE = 'silent_mode'
REGEX_MAX_BLACK_LIST = 'regex_max_black_list'
REGEX_MAX_TIME_LIMIT = 'regex_max_time_limit'
SNAPSHOT_AUTO_UPDATE_INTERVAL = 'snapshot_auto_update_interval'
SILENT_MODE = 'silent_mode'

class Client:
_context: Context = Context.empty()
_switcher: dict[str, Switcher] = {}
_snapshot_auto_updater: SnapshotAutoUpdater = SnapshotAutoUpdater()
_snapshot_watcher: SnapshotWatcher = SnapshotWatcher()

# pylint: disable=too-many-arguments
@staticmethod
def build_context(
domain: str,
def build_context(*,
domain: str,
url: Optional[str] = None,
api_key: Optional[str] = None,
component: Optional[str] = None,
environment: Optional[str] = DEFAULT_ENVIRONMENT,
options = ContextOptions()):
"""
Build the context for the client
"""
Build the context for the client

:param domain: Domain name
:param url:Switcher-API URL
:param api_key: Switcher-API key generated for the application/component
:param component: Application/component name
:param environment: Environment name
:param options: Optional parameters

"""
Client._context = Context(domain, url, api_key, component, environment, options)
Client._context = Context(
domain=domain, url=url,
api_key=api_key,
component=component,
environment=environment,
options=options)

# Default values
GlobalSnapshot.clear()
Expand All @@ -59,12 +65,12 @@ def build_context(
@staticmethod
def _build_options(options: ContextOptions):
options_handler = {
SwitcherOptions.SNAPSHOT_AUTO_UPDATE_INTERVAL: lambda: Client.schedule_snapshot_auto_update(),
SwitcherOptions.SILENT_MODE: lambda: Client._init_silent_mode(get(options.silent_mode, '')),
SwitcherOptions.REGEX_MAX_BLACK_LIST: lambda: TimedMatch.set_max_blacklisted(options.regex_max_black_list),
SwitcherOptions.REGEX_MAX_TIME_LIMIT: lambda: TimedMatch.set_max_time_limit(options.regex_max_time_limit)
SNAPSHOT_AUTO_UPDATE_INTERVAL: Client.schedule_snapshot_auto_update,
SILENT_MODE: lambda: Client._init_silent_mode(get(options.silent_mode, '')),
REGEX_MAX_BLACK_LIST: lambda: TimedMatch.set_max_blacklisted(options.regex_max_black_list),
REGEX_MAX_TIME_LIMIT: lambda: TimedMatch.set_max_time_limit(options.regex_max_time_limit)
}

for option_key, handler in options_handler.items():
if hasattr(options, option_key) and getattr(options, option_key) is not None:
handler()
Expand All @@ -78,7 +84,7 @@ def _init_silent_mode(silent_mode: str):

@staticmethod
def get_switcher(key: Optional[str] = None) -> Switcher:
"""
"""
Creates a new instance of Switcher.
Provide a key if you want to persist the instance.
"""
Expand All @@ -87,15 +93,15 @@ def get_switcher(key: Optional[str] = None) -> Switcher:

if persisted_switcher is not None:
return persisted_switcher

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

if key_value != '':
Client._switcher[key_value] = switcher

return switcher


@staticmethod
def load_snapshot(options: Optional[LoadSnapshotOptions] = None) -> int:
Expand All @@ -111,7 +117,7 @@ def load_snapshot(options: Optional[LoadSnapshotOptions] = None) -> int:
Client.check_snapshot()

return Client.snapshot_version()

@staticmethod
def check_snapshot():
""" Verifies if the current snapshot file is updated
Expand All @@ -129,18 +135,18 @@ def check_snapshot():
if snapshot is not None:
if Client._context.options.snapshot_location is not None:
save_snapshot(
snapshot=snapshot,
snapshot_location=get(Client._context.options.snapshot_location, ''),
snapshot=snapshot,
snapshot_location=get(Client._context.options.snapshot_location, ''),
environment=get(Client._context.environment, DEFAULT_ENVIRONMENT)
)

GlobalSnapshot.init(snapshot)
return True

return False

@staticmethod
def schedule_snapshot_auto_update(interval: Optional[int] = None,
def schedule_snapshot_auto_update(interval: Optional[int] = None,
callback: Optional[Callable[[Optional[Exception], bool], None]] = None):
""" Schedule Snapshot auto update """
callback = get(callback, lambda *_: None)
Expand All @@ -155,7 +161,7 @@ def schedule_snapshot_auto_update(interval: Optional[int] = None,
check_snapshot=Client.check_snapshot,
callback=callback
)

@staticmethod
def terminate_snapshot_auto_update():
""" Terminate Snapshot auto update """
Expand All @@ -166,10 +172,11 @@ def watch_snapshot(callback: Optional[WatchSnapshotCallback] = None) -> None:
""" Watch snapshot file for changes and invoke callbacks on result """
callback = get(callback, WatchSnapshotCallback())
snapshot_location = Client._context.options.snapshot_location

if snapshot_location is None:
return callback.reject(Exception("Snapshot location is not defined in the context options"))

callback.reject(SnapshpotNotFoundError("Snapshot location is not defined in the context options"))
return

environment = get(Client._context.environment, DEFAULT_ENVIRONMENT)
Client._snapshot_watcher.watch_snapshot(snapshot_location, environment, callback)

Expand All @@ -185,9 +192,9 @@ def snapshot_version() -> int:

if snapshot is None:
return 0

return snapshot.domain.version

@staticmethod
def check_switchers(switcher_keys: list[str]) -> None:
""" Verifies if switchers are properly configured """
Expand All @@ -199,7 +206,7 @@ def check_switchers(switcher_keys: list[str]) -> None:
@staticmethod
def get_execution(switcher: Switcher) -> ExecutionLogger:
"""Retrieve execution log given a switcher"""
return ExecutionLogger.get_execution(switcher._key, switcher._input)
return ExecutionLogger.get_execution(switcher.key, switcher.inputs)

@staticmethod
def clear_logger() -> None:
Expand All @@ -226,11 +233,11 @@ def subscribe_notify_error(callback: Callable[[Exception], None]) -> None:
@staticmethod
def _is_check_snapshot_available(fetch_remote = False) -> bool:
return Client.snapshot_version() == 0 and (fetch_remote or not Client._context.options.local)

@staticmethod
def _check_switchers_remote(switcher_keys: list[str]) -> None:
RemoteAuth.auth()
Remote.check_switchers(
token=GlobalAuth.get_token(),
switcher_keys=switcher_keys,
context=Client._context)
token=GlobalAuth.get_token(),
switcher_keys=switcher_keys,
context=Client._context)
14 changes: 9 additions & 5 deletions switcher_client/errors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ def __init__(self, message):
super().__init__(self.message)

class RemoteAuthError(RemoteError):
def __init__(self, message):
super().__init__(message)
pass

class RemoteCriteriaError(RemoteError):
def __init__(self, message):
super().__init__(message)
pass

class RemoteSwitcherError(RemoteError):
def __init__(self, not_found: list):
Expand All @@ -25,11 +23,17 @@ def __init__(self, message):
self.message = message
super().__init__(self.message)

class SnapshpotNotFoundError(Exception):
def __init__(self, message):
self.message = message
super().__init__(self.message)

__all__ = [
'RemoteError',
'RemoteAuthError',
'RemoteCriteriaError',
'RemoteSwitcherError',
'LocalSwitcherError',
'LocalCriteriaError',
]
'SnapshpotNotFoundError'
]
2 changes: 1 addition & 1 deletion switcher_client/lib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
__all__ = [
'RemoteAuth',
'ResultDetail',
]
]
2 changes: 1 addition & 1 deletion switcher_client/lib/globals/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
"Context",
"ContextOptions",
"RetryOptions",
]
]
6 changes: 3 additions & 3 deletions switcher_client/lib/globals/global_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ def init():
@staticmethod
def get_token():
return GlobalAuth.__token

@staticmethod
def get_exp():
return GlobalAuth.__exp

@staticmethod
def set_token(token: str):
GlobalAuth.__token = token

@staticmethod
def set_exp(exp: str):
GlobalAuth.__exp = exp
GlobalAuth.__exp = exp
Loading
Loading