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: 1 addition & 1 deletion switcher_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from switcher_client.lib.remote_auth import RemoteAuth
from switcher_client.lib.globals.global_context import Context, ContextOptions
from switcher_client.lib.globals.global_context import DEFAULT_ENVIRONMENT
from switcher_client.lib.snapshot import load_domain
from switcher_client.lib.snapshot_loader import load_domain
from switcher_client.lib.utils import get
from switcher_client.switcher import Switcher

Expand Down
16 changes: 0 additions & 16 deletions switcher_client/lib/snapshot.py

This file was deleted.

100 changes: 100 additions & 0 deletions switcher_client/lib/snapshot_loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import json
import os

from switcher_client.lib.types import Snapshot, SnapshotData, Domain, Group, Config, StrategyConfig, Relay

def load_domain(snapshot_location: str, environment: str):
""" Load Domain from snapshot file """

snapshot_file = f"{snapshot_location}/{environment}.json"
json_data = {}

if not os.path.exists(snapshot_file):
json_data = {
'data': {
'domain': {
'version': 0,
}
}
}

if snapshot_location:
os.makedirs(snapshot_location, exist_ok=True)
with open(snapshot_file, 'w') as file:
json.dump(json_data, file, indent=4)

elif os.path.exists(snapshot_file):
with open(snapshot_file, 'r') as file:
json_data = json.load(file)

snapshot = Snapshot()
snapshot.data = SnapshotData()
snapshot.data.domain = _parse_domain(json_data['data']['domain'])

return snapshot

def _parse_domain(domain_data: dict) -> Domain:
""" Parse domain data from JSON """

domain = Domain()
domain.name = domain_data.get('name')
domain.activated = domain_data.get('activated')
domain.version = domain_data.get('version', 0)

if 'group' in domain_data and domain_data['group']:
domain.group = []
for group_data in domain_data['group']:
domain.group.append(_parse_group(group_data))

return domain

def _parse_group(group_data: dict) -> Group:
""" Parse group data from JSON """

group = Group()
group.name = group_data.get('name')
group.activated = group_data.get('activated')

if 'config' in group_data and group_data['config']:
group.config = []
for config_data in group_data['config']:
group.config.append(_parse_config(config_data))

return group

def _parse_config(config_data: dict) -> Config:
""" Parse config data from JSON """

config = Config()
config.key = config_data.get('key')
config.activated = config_data.get('activated')

if 'strategies' in config_data and config_data['strategies']:
config.strategies = []
for strategy_data in config_data['strategies']:
config.strategies.append(_parse_strategy(strategy_data))

if 'relay' in config_data and config_data['relay']:
config.relay = _parse_relay(config_data['relay'])

return config

def _parse_strategy(strategy_data: dict) -> StrategyConfig:
""" Parse strategy data from JSON """

strategy = StrategyConfig()
strategy.strategy = strategy_data.get('strategy')
strategy.activated = strategy_data.get('activated')
strategy.operation = strategy_data.get('operation')
strategy.values = strategy_data.get('values')

return strategy

def _parse_relay(relay_data: dict) -> Relay:
""" Parse relay data from JSON """

relay = Relay()
relay.type = relay_data.get('type')
relay.activated = relay_data.get('activated')

return relay
24 changes: 24 additions & 0 deletions switcher_client/lib/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,29 @@ def __init__(self):
self.name: Optional[str] = None
self.version: int = 0
self.activated: Optional[bool] = None
self.group: Optional[List[Group]] = None

class Group:
def __init__(self):
self.name: Optional[str] = None
self.activated: Optional[bool] = None
self.config: Optional[List[Config]] = None

class Config:
def __init__(self):
self.key: Optional[str] = None
self.activated: Optional[bool] = None
self.strategies: Optional[List[StrategyConfig]] = None
self.relay: Optional[Relay] = None

class StrategyConfig:
def __init__(self):
self.strategy: Optional[str] = None
self.activated: Optional[bool] = None
self.operation: Optional[str] = None
self.values: Optional[List[str]] = None

class Relay:
def __init__(self):
self.type: Optional[str] = None
self.activated: Optional[bool] = None
162 changes: 162 additions & 0 deletions tests/snapshots/default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
{
"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
},
"components": []
},
{
"key": "USECASE104",
"description": "Relay disabled",
"relay": {
"type": "VALIDATOR",
"activated": false
},
"activated": true,
"components": []
}
]
}
]
}
}
}
39 changes: 35 additions & 4 deletions tests/test_client_context.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import pytest

from switcher_client import Client, ContextOptions
Expand Down Expand Up @@ -44,14 +45,14 @@ def test_context_with_optionals():
domain='My Domain',
options=ContextOptions(
local=True,
snapshot_location='./snapshots'
snapshot_location='./tests/snapshots'
)
)

options = Client.context.options

assert options.local == True
assert options.snapshot_location == './snapshots'
assert options.snapshot_location == './tests/snapshots'

def test_load_from_snapshot():
""" Should load Domain from snapshot file """
Expand All @@ -60,7 +61,7 @@ def test_load_from_snapshot():
domain='My Domain',
options=ContextOptions(
local=True,
snapshot_location='./snapshots'
snapshot_location='./tests/snapshots'
)
)

Expand All @@ -70,4 +71,34 @@ def test_load_from_snapshot():
# test
version = Client.load_snapshot()
assert Client.snapshot_version() == 1
assert version == Client.snapshot_version()
assert version == Client.snapshot_version()

def test_load_from_snapshot_empty():
""" Should create clean snapshot when no snapshot file exists """

Client.build_context(
domain='My Domain',
environment='generated-clean',
options=ContextOptions(
local=True,
snapshot_location='./tests/snapshots'
)
)

# verify initial snapshot version
assert Client.snapshot_version() == 0

# test
version = Client.load_snapshot()
assert Client.snapshot_version() == 0
assert version == Client.snapshot_version()

# tear down
delete_snapshot_file('./tests/snapshots', 'generated-clean')

# Helpers

def delete_snapshot_file(snapshot_location: str, environment: str):
snapshot_file = f"{snapshot_location}/{environment}.json"
if os.path.exists(snapshot_file):
os.remove(snapshot_file)
Loading