Skip to content

Commit 0a9c386

Browse files
committed
feat: Add flag overrides to the contract test services
Both contract test services advertise the flag-overrides capability and accept the overrides object in the SDK configuration: filePaths, and the optional duplicateKeysHandling, changeDetection, and pollIntervalMs properties, which configure the file-based override source on the FDv2 data system. An overrides object without a data system configuration is rejected, because overrides are an option of the FDv2 data system only.
1 parent 2074b74 commit 0a9c386

4 files changed

Lines changed: 44 additions & 5 deletions

File tree

‎contract-tests/async_client_entity.py‎

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
)
2727
from ldclient.impl.util import Result
2828
from ldclient.integrations import Redis
29+
from ldclient.integrations.overrides import FileOverrideSourceBuilder
2930
from ldclient.interfaces import DataStoreMode
3031
from ldclient.migrations import (
3132
AsyncMigratorBuilder,
@@ -56,8 +57,10 @@ async def start(self):
5657
}
5758

5859
datasystem_config = config_params.get('dataSystem')
60+
if config_params.get('overrides') is not None and datasystem_config is None:
61+
raise ValueError("flag overrides require the data system to be configured")
5962
if datasystem_config is not None:
60-
opts["datasystem_config"] = _build_async_data_system(datasystem_config, opts)
63+
opts["datasystem_config"] = _build_async_data_system(datasystem_config, opts, config_params.get('overrides'))
6164
elif config_params.get("streaming") is not None:
6265
streaming = config_params["streaming"]
6366
if streaming.get("baseUri") is not None:
@@ -297,13 +300,13 @@ def _set_optional_value(params_in: dict, name_in: str, func: Callable[[Any], Any
297300
func(params_in[name_in])
298301

299302

300-
def _build_async_data_system(datasystem_config: dict, opts: dict) -> AsyncDataSystemConfig:
303+
def _build_async_data_system(datasystem_config: dict, opts: dict, overrides_config: Optional[dict] = None) -> AsyncDataSystemConfig:
301304
"""Build an AsyncDataSystemConfig from the harness's dataSystem config.
302305
303306
Wires the FDv2 initializers, the ordered synchronizer chain, the FDv1
304-
fallback synchronizer, the payload filter, and an optional async
305-
persistent store. The async client injects its shared aiohttp session
306-
into these builders when it starts.
307+
fallback synchronizer, the payload filter, an optional async persistent
308+
store, and the optional file-based override source. The async client
309+
injects its shared aiohttp session into these builders when it starts.
307310
"""
308311
initializers: Optional[list] = None
309312
init_configs = datasystem_config.get('initializers')
@@ -356,6 +359,9 @@ def _build_async_data_system(datasystem_config: dict, opts: dict) -> AsyncDataSy
356359
"fdv1_fallback_synchronizer": fdv1_fallback_synchronizer,
357360
}
358361

362+
if overrides_config is not None:
363+
ds_kwargs["override_source"] = _make_override_source(overrides_config)
364+
359365
store_config = datasystem_config.get("store")
360366
if store_config is not None:
361367
persistent_store_config = store_config.get("persistentDataStore")
@@ -370,6 +376,18 @@ def _build_async_data_system(datasystem_config: dict, opts: dict) -> AsyncDataSy
370376
return AsyncDataSystemConfig(**ds_kwargs)
371377

372378

379+
def _make_override_source(params: dict) -> FileOverrideSourceBuilder:
380+
"""
381+
Builds the file-based override source from the harness's overrides configuration. The
382+
files are written by the harness on a file system shared with this service.
383+
"""
384+
builder = FileOverrideSourceBuilder(params["filePaths"])
385+
_set_optional_value(params, "duplicateKeysHandling", builder.duplicate_keys_handling)
386+
_set_optional_value(params, "changeDetection", builder.change_detection)
387+
_set_optional_time(params, "pollIntervalMs", builder.poll_interval)
388+
return builder
389+
390+
373391
def _create_async_persistent_store(persistent_store_config: dict):
374392
"""Create an async persistent feature store from the harness config.
375393

‎contract-tests/async_service.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ async def handle_status(request: aiohttp.web.Request) -> aiohttp.web.Response:
6969
'migrations',
7070
'persistent-data-store-redis',
7171
'fdv1-fallback',
72+
'flag-overrides',
7273
]
7374
}
7475
return aiohttp.web.Response(

‎contract-tests/client_entity.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from ldclient.feature_store import CacheConfig
2929
from ldclient.impl.datasourcev2.polling import PollingDataSourceBuilder
3030
from ldclient.integrations import Consul, DynamoDB, Redis
31+
from ldclient.integrations.overrides import FileOverrideSourceBuilder
3132
from ldclient.interfaces import DataStoreMode
3233

3334

@@ -44,6 +45,8 @@ def __init__(self, tag, config):
4445
}
4546

4647
datasystem_config = config.get('dataSystem')
48+
if config.get('overrides') is not None and datasystem_config is None:
49+
raise ValueError("flag overrides require the data system to be configured")
4750
if datasystem_config is not None:
4851
datasystem = custom()
4952

@@ -107,6 +110,10 @@ def __init__(self, tag, config):
107110

108111
datasystem.data_store(store, store_mode)
109112

113+
overrides_config = config.get('overrides')
114+
if overrides_config is not None:
115+
datasystem.overrides(_make_override_source(overrides_config))
116+
110117
opts["datasystem_config"] = datasystem.build()
111118

112119
elif config.get("streaming") is not None:
@@ -325,6 +332,18 @@ def _set_optional_value(params_in: dict, name_in: str, func: Callable[[Any], Any
325332
func(params_in[name_in])
326333

327334

335+
def _make_override_source(params: dict) -> FileOverrideSourceBuilder:
336+
"""
337+
Builds the file-based override source from the harness's overrides configuration. The
338+
files are written by the harness on a file system shared with this service.
339+
"""
340+
builder = FileOverrideSourceBuilder(params["filePaths"])
341+
_set_optional_value(params, "duplicateKeysHandling", builder.duplicate_keys_handling)
342+
_set_optional_value(params, "changeDetection", builder.change_detection)
343+
_set_optional_time(params, "pollIntervalMs", builder.poll_interval)
344+
return builder
345+
346+
328347
def _create_persistent_store(persistent_store_config: dict):
329348
"""
330349
Creates a persistent store instance based on the configuration.

‎contract-tests/service.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ def status():
8686
'flag-change-listeners',
8787
'flag-value-change-listeners',
8888
'fdv1-fallback',
89+
'flag-overrides',
8990
]
9091
}
9192
return json.dumps(body), 200, {'Content-type': 'application/json'}

0 commit comments

Comments
 (0)