From c0ccfc2d84ff9939d019bac69431c92e95695ea1 Mon Sep 17 00:00:00 2001 From: petruki <31597636+petruki@users.noreply.github.com> Date: Sun, 5 Oct 2025 16:30:12 -0700 Subject: [PATCH] Removed data wrapper from Snapshot to standardize schema --- switcher_client/client.py | 2 +- switcher_client/lib/remote.py | 2 +- switcher_client/lib/resolver.py | 10 +- switcher_client/lib/snapshot_loader.py | 10 +- switcher_client/lib/types.py | 10 +- tests/playground/index.py | 4 +- tests/snapshots/default.json | 312 +++++++++++----------- tests/snapshots/default_disabled.json | 14 +- tests/snapshots/default_load_1.json | 44 ++- tests/snapshots/default_load_2.json | 44 ++- tests/test_client_load_snapshot_remote.py | 4 +- 11 files changed, 220 insertions(+), 236 deletions(-) diff --git a/switcher_client/client.py b/switcher_client/client.py index c879f37..051d05b 100644 --- a/switcher_client/client.py +++ b/switcher_client/client.py @@ -134,7 +134,7 @@ def snapshot_version() -> int: if snapshot is None: return 0 - return snapshot.data.domain.version + return snapshot.domain.version @staticmethod def __is_check_snapshot_available(fetch_remote = False) -> bool: diff --git a/switcher_client/lib/remote.py b/switcher_client/lib/remote.py index f525ce1..508d1e1 100644 --- a/switcher_client/lib/remote.py +++ b/switcher_client/lib/remote.py @@ -82,7 +82,7 @@ def resolve_snapshot(token: Optional[str], context: Context) -> str | None: response = Remote.__do_post(f'{context.url}/graphql', data, Remote.__get_header(token)) if response.status_code == 200: - return json.dumps(response.json(), indent=4) + return json.dumps(response.json().get('data', {}), indent=4) raise RemoteError(f'[resolve_snapshot] failed with status: {response.status_code}') diff --git a/switcher_client/lib/resolver.py b/switcher_client/lib/resolver.py index 58eabe4..e4d0fc7 100644 --- a/switcher_client/lib/resolver.py +++ b/switcher_client/lib/resolver.py @@ -1,5 +1,5 @@ from switcher_client.errors import LocalCriteriaError -from switcher_client.lib.types import Config, Group, ResultDetail, Snapshot, SnapshotData +from switcher_client.lib.types import Config, Domain, Group, ResultDetail, Snapshot from switcher_client.switcher_data import SwitcherData class Resolver: @@ -9,14 +9,14 @@ def check_criteria(snapshot: Snapshot | None, switcher: SwitcherData) -> ResultD if not snapshot: raise LocalCriteriaError("Snapshot not loaded. Try to use 'Client.load_snapshot()'") - return Resolver.__check_domain(snapshot.data, switcher) + return Resolver.__check_domain(snapshot.domain, switcher) @staticmethod - def __check_domain(data: SnapshotData, switcher: SwitcherData) -> ResultDetail: - if data.domain.activated is False: + def __check_domain(domain: Domain, switcher: SwitcherData) -> ResultDetail: + if domain.activated is False: return ResultDetail.disabled("Domain is disabled") - return Resolver.__check_group(data.domain.group, switcher) + return Resolver.__check_group(domain.group, switcher) @staticmethod def __check_group(groups: list[Group], switcher: SwitcherData) -> ResultDetail: diff --git a/switcher_client/lib/snapshot_loader.py b/switcher_client/lib/snapshot_loader.py index 32985c0..d607d1b 100644 --- a/switcher_client/lib/snapshot_loader.py +++ b/switcher_client/lib/snapshot_loader.py @@ -14,10 +14,8 @@ def load_domain(snapshot_location: str, environment: str): if not os.path.exists(snapshot_file): json_data = { - 'data': { - 'domain': { - 'version': 0, - } + 'domain': { + 'version': 0, } } @@ -30,7 +28,7 @@ def load_domain(snapshot_location: str, environment: str): with open(snapshot_file, 'r') as file: json_data = json.load(file) - snapshot = Snapshot(json_data.get('data', {})) + snapshot = Snapshot(json_data.get('domain', {})) return snapshot @@ -48,7 +46,7 @@ def validate_snapshot( if not status: snapshot_str = Remote.resolve_snapshot(GlobalAuth.get_token(), context) graphql_response = json.loads(snapshot_str or '{}') - return Snapshot(graphql_response.get('data', '{}')) + return Snapshot(graphql_response.get('domain', '{}')) return None diff --git a/switcher_client/lib/types.py b/switcher_client/lib/types.py index 8115d97..cbb1f93 100644 --- a/switcher_client/lib/types.py +++ b/switcher_client/lib/types.py @@ -14,10 +14,6 @@ def disabled(reason: str, metadata: Optional[dict] = None) -> 'ResultDetail': def success(reason: str = "Success", metadata: Optional[dict] = None) -> 'ResultDetail': return ResultDetail(result=True, reason=reason, metadata=metadata) -class SnapshotData: - def __init__(self): - self.domain: Domain - class Domain: def __init__(self): self.name: str @@ -52,10 +48,8 @@ def __init__(self): class Snapshot: def __init__(self, json_data: dict): - data = json_data self._original_data = json_data - self.data = SnapshotData() - self.data.domain = self._parse_domain(data.get('domain', {})) + self.domain = self._parse_domain(json_data) def _parse_domain(self, domain_data: dict) -> Domain: """ Parse domain data from JSON """ @@ -126,4 +120,4 @@ def _parse_relay(self, relay_data: dict) -> Relay: def to_dict(self) -> dict: """ Convert Snapshot back to dictionary format for JSON serialization """ - return {'data': self._original_data} \ No newline at end of file + return {'domain': self._original_data} \ No newline at end of file diff --git a/tests/playground/index.py b/tests/playground/index.py index 729b03b..6627fde 100644 --- a/tests/playground/index.py +++ b/tests/playground/index.py @@ -37,7 +37,7 @@ def load_snapshot_from_remote(): setup_context(ContextOptions( local=True, - snapshot_location='snapshots/temp' + snapshot_location='tests/playground/snapshots/temp' )) Client.load_snapshot(LoadSnapshotOptions( @@ -50,7 +50,7 @@ def auto_update_snapshot(): """ Use case: Auto update snapshot """ setup_context(ContextOptions( local=True, - snapshot_location='snapshots/temp', + snapshot_location='tests/playground/snapshots/temp', snapshot_auto_update_interval=10 )) diff --git a/tests/snapshots/default.json b/tests/snapshots/default.json index e295ea8..3d42a3f 100644 --- a/tests/snapshots/default.json +++ b/tests/snapshots/default.json @@ -1,162 +1,160 @@ { - "data": { - "domain": { - "name": "Business", - "description": "Business description", - "activated": true, - "version": 1, - "group": [ - { - "name": "Rollout 2020", - "description": "Changes that will be applied during the rollout", - "activated": true, - "config": [ - { - "key": "FF2FOR2020", - "description": "Feature Flag", - "activated": true, - "strategies": [ - { - "strategy": "NETWORK_VALIDATION", - "activated": true, - "operation": "EXIST", - "values": [ - "10.0.0.3/24" - ] - }, - { - "strategy": "VALUE_VALIDATION", - "activated": true, - "operation": "NOT_EXIST", - "values": [ - "USA", - "Canada", - "Australia", - "Africa" - ] - } - ], - "components": [] - }, - { - "key": "FF2FOR2021", - "description": "Strategy disabled", - "activated": true, - "strategies": [ - { - "strategy": "NETWORK_VALIDATION", - "activated": false, - "operation": "EXIST", - "values": [ - "10.0.0.3/24" - ] - } - ], - "components": [] - }, - { - "key": "FF2FOR2022", - "description": "No strategies", - "activated": true, - "components": [] - }, - { - "key": "FF2FOR2023", - "description": "Feature Flag - Payload Strategy", - "activated": true, - "strategies": [ - { - "strategy": "PAYLOAD_VALIDATION", - "activated": true, - "operation": "HAS_ALL", - "values": [ - "id", "user", "user.login", "user.role" - ] - } - ], - "components": [] - }, - { - "key": "FF2FOR2024", - "description": "reDOS safe test", - "activated": true, - "strategies": [ - { - "strategy": "REGEX_VALIDATION", - "activated": true, - "operation": "EXIST", - "values": [ - "^(([a-z])+.)+[A-Z]([a-z])+$" - ] - } - ], - "components": [] - } - ] - }, - { - "name": "Rollout 2030", - "description": "Changes that will be applied during the rollout", - "activated": true, - "config": [ - { - "key": "FF2FOR2030", - "description": "Feature Flag", - "activated": true, - "strategies": [], - "components": [] - }, - { - "key": "FF2FOR2031", - "description": "Feature Flag disabled", - "activated": false, - "strategies": [], - "components": [] - } - ] - }, - { - "name": "Rollout 2040", - "description": "Project is disabled", - "activated": false, - "config": [ - { - "key": "FF2FOR2040", - "description": "Feature Flag", - "activated": true, - "strategies": [], - "components": [] - } - ] - }, - { - "name": "Relay test", - "description": "Relay group", - "activated": true, - "config": [ - { - "key": "USECASE103", - "description": "Relay enabled", - "activated": true, - "relay": { - "type": "VALIDATOR", - "activated": true + "domain": { + "name": "Business", + "description": "Business description", + "activated": true, + "version": 1, + "group": [ + { + "name": "Rollout 2020", + "description": "Changes that will be applied during the rollout", + "activated": true, + "config": [ + { + "key": "FF2FOR2020", + "description": "Feature Flag", + "activated": true, + "strategies": [ + { + "strategy": "NETWORK_VALIDATION", + "activated": true, + "operation": "EXIST", + "values": [ + "10.0.0.3/24" + ] }, - "components": [] + { + "strategy": "VALUE_VALIDATION", + "activated": true, + "operation": "NOT_EXIST", + "values": [ + "USA", + "Canada", + "Australia", + "Africa" + ] + } + ], + "components": [] + }, + { + "key": "FF2FOR2021", + "description": "Strategy disabled", + "activated": true, + "strategies": [ + { + "strategy": "NETWORK_VALIDATION", + "activated": false, + "operation": "EXIST", + "values": [ + "10.0.0.3/24" + ] + } + ], + "components": [] + }, + { + "key": "FF2FOR2022", + "description": "No strategies", + "activated": true, + "components": [] + }, + { + "key": "FF2FOR2023", + "description": "Feature Flag - Payload Strategy", + "activated": true, + "strategies": [ + { + "strategy": "PAYLOAD_VALIDATION", + "activated": true, + "operation": "HAS_ALL", + "values": [ + "id", "user", "user.login", "user.role" + ] + } + ], + "components": [] + }, + { + "key": "FF2FOR2024", + "description": "reDOS safe test", + "activated": true, + "strategies": [ + { + "strategy": "REGEX_VALIDATION", + "activated": true, + "operation": "EXIST", + "values": [ + "^(([a-z])+.)+[A-Z]([a-z])+$" + ] + } + ], + "components": [] + } + ] + }, + { + "name": "Rollout 2030", + "description": "Changes that will be applied during the rollout", + "activated": true, + "config": [ + { + "key": "FF2FOR2030", + "description": "Feature Flag", + "activated": true, + "strategies": [], + "components": [] + }, + { + "key": "FF2FOR2031", + "description": "Feature Flag disabled", + "activated": false, + "strategies": [], + "components": [] + } + ] + }, + { + "name": "Rollout 2040", + "description": "Project is disabled", + "activated": false, + "config": [ + { + "key": "FF2FOR2040", + "description": "Feature Flag", + "activated": true, + "strategies": [], + "components": [] + } + ] + }, + { + "name": "Relay test", + "description": "Relay group", + "activated": true, + "config": [ + { + "key": "USECASE103", + "description": "Relay enabled", + "activated": true, + "relay": { + "type": "VALIDATOR", + "activated": true }, - { - "key": "USECASE104", - "description": "Relay disabled", - "relay": { - "type": "VALIDATOR", - "activated": false - }, - "activated": true, - "components": [] - } - ] - } - ] - } + "components": [] + }, + { + "key": "USECASE104", + "description": "Relay disabled", + "relay": { + "type": "VALIDATOR", + "activated": false + }, + "activated": true, + "components": [] + } + ] + } + ] } } \ No newline at end of file diff --git a/tests/snapshots/default_disabled.json b/tests/snapshots/default_disabled.json index 6f37aa9..2dc6440 100644 --- a/tests/snapshots/default_disabled.json +++ b/tests/snapshots/default_disabled.json @@ -1,11 +1,9 @@ { - "data": { - "domain": { - "name": "Business", - "description": "Business description", - "activated": false, - "version": 1, - "group": [] - } + "domain": { + "name": "Business", + "description": "Business description", + "activated": false, + "version": 1, + "group": [] } } \ No newline at end of file diff --git a/tests/snapshots/default_load_1.json b/tests/snapshots/default_load_1.json index 17dd1d2..ed53063 100644 --- a/tests/snapshots/default_load_1.json +++ b/tests/snapshots/default_load_1.json @@ -1,26 +1,24 @@ { - "data": { - "domain": { - "name": "Business", - "description": "Business description", - "version": 1588557288040, - "activated": true, - "group": [ - { - "name": "Rollout 2030", - "description": "Changes that will be applied during the rollout", - "activated": true, - "config": [ - { - "key": "FF2FOR2030", - "description": "Feature Flag", - "activated": true, - "strategies": [], - "components": [] - } - ] - } - ] - } + "domain": { + "name": "Business", + "description": "Business description", + "version": 1588557288040, + "activated": true, + "group": [ + { + "name": "Rollout 2030", + "description": "Changes that will be applied during the rollout", + "activated": true, + "config": [ + { + "key": "FF2FOR2030", + "description": "Feature Flag", + "activated": true, + "strategies": [], + "components": [] + } + ] + } + ] } } \ No newline at end of file diff --git a/tests/snapshots/default_load_2.json b/tests/snapshots/default_load_2.json index 7064e76..941ac97 100644 --- a/tests/snapshots/default_load_2.json +++ b/tests/snapshots/default_load_2.json @@ -1,26 +1,24 @@ { - "data": { - "domain": { - "name": "Business", - "description": "Business description", - "version": 1588557288041, - "activated": true, - "group": [ - { - "name": "Rollout 2030", - "description": "Changes that will be applied during the rollout", - "activated": true, - "config": [ - { - "key": "FF2FOR2030", - "description": "Feature Flag", - "activated": false, - "strategies": [], - "components": [] - } - ] - } - ] - } + "domain": { + "name": "Business", + "description": "Business description", + "version": 1588557288041, + "activated": true, + "group": [ + { + "name": "Rollout 2030", + "description": "Changes that will be applied during the rollout", + "activated": true, + "config": [ + { + "key": "FF2FOR2030", + "description": "Feature Flag", + "activated": false, + "strategies": [], + "components": [] + } + ] + } + ] } } \ No newline at end of file diff --git a/tests/test_client_load_snapshot_remote.py b/tests/test_client_load_snapshot_remote.py index 5c85fca..56e3552 100644 --- a/tests/test_client_load_snapshot_remote.py +++ b/tests/test_client_load_snapshot_remote.py @@ -205,7 +205,7 @@ def given_resolve_snapshot(httpx_mock: HTTPXMock, status_code=200, data=[], is_r url='https://api.switcherapi.com/graphql', method='POST', status_code=status_code, - json={'data': data}, + json={'data': { 'domain': data}}, is_reusable=is_reusable ) @@ -229,7 +229,7 @@ def given_context(url='https://api.switcherapi.com', def load_snapshot_fixture(file_path: str): with open(file_path, 'r') as f: - return json.loads(f.read()).get('data', {}) + return json.loads(f.read()).get('domain', {}) def delete_snapshot_file(snapshot_location: str, environment: str): snapshot_file = f"{snapshot_location}/{environment}.json"