Skip to content

Commit 4804ac1

Browse files
committed
fix: Fix data and target availability for daemon mode
1 parent 2b7eedc commit 4804ac1

File tree

4 files changed

+243
-4
lines changed

4 files changed

+243
-4
lines changed

ldclient/impl/datasystem/fdv1.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@ def data_availability(self) -> DataAvailability:
134134
if self._config.offline:
135135
return DataAvailability.DEFAULTS
136136

137+
if self._config.use_ldd:
138+
return DataAvailability.CACHED \
139+
if self._store_wrapper.initialized \
140+
else DataAvailability.DEFAULTS
141+
137142
if self._update_processor is not None and self._update_processor.initialized():
138143
return DataAvailability.REFRESHED
139144

@@ -146,7 +151,9 @@ def data_availability(self) -> DataAvailability:
146151
def target_availability(self) -> DataAvailability:
147152
if self._config.offline:
148153
return DataAvailability.DEFAULTS
149-
# In LDD mode or normal connected modes, the ideal is to be refreshed
154+
if self._config.use_ldd:
155+
return DataAvailability.CACHED
156+
150157
return DataAvailability.REFRESHED
151158

152159
def _make_update_processor(self, config: Config, store: FeatureStore, ready: Event):

ldclient/impl/datasystem/fdv2.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,6 @@ def __init__(
267267
self._primary_synchronizer_builder: Optional[Builder[Synchronizer]] = data_system_config.primary_synchronizer
268268
self._secondary_synchronizer_builder = data_system_config.secondary_synchronizer
269269
self._fdv1_fallback_synchronizer_builder = data_system_config.fdv1_fallback_synchronizer
270-
self._disabled = self._config.offline
271270

272271
# Diagnostic accumulator provided by client for streaming metrics
273272
self._diagnostic_accumulator: Optional[DiagnosticAccumulator] = None
@@ -319,7 +318,7 @@ def start(self, set_on_ready: Event):
319318
320319
:param set_on_ready: Event to set when the system is ready or has failed
321320
"""
322-
if self._disabled:
321+
if self._config.offline:
323322
log.warning("Data system is disabled, SDK will return application-defined default values")
324323
set_on_ready.set()
325324
return
@@ -688,15 +687,21 @@ def data_availability(self) -> DataAvailability:
688687
if self._store.selector().is_defined():
689688
return DataAvailability.REFRESHED
690689

691-
if not self._configured_with_data_sources or self._store.is_initialized():
690+
if self._store.is_initialized():
692691
return DataAvailability.CACHED
693692

694693
return DataAvailability.DEFAULTS
695694

696695
@property
697696
def target_availability(self) -> DataAvailability:
698697
"""Get the target data availability level based on configuration."""
698+
if self._config.offline:
699+
return DataAvailability.DEFAULTS
700+
699701
if self._configured_with_data_sources:
700702
return DataAvailability.REFRESHED
701703

704+
if self._data_system_config.data_store is None:
705+
return DataAvailability.DEFAULTS
706+
702707
return DataAvailability.CACHED
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# pylint: disable=missing-docstring
2+
3+
from threading import Event
4+
5+
from ldclient.config import Config
6+
from ldclient.feature_store import InMemoryFeatureStore
7+
from ldclient.impl.datasystem import DataAvailability
8+
from ldclient.impl.datasystem.fdv1 import FDv1
9+
from ldclient.versioned_data_kind import FEATURES
10+
11+
12+
def test_fdv1_availability_offline():
13+
"""Test that FDv1 returns DEFAULTS for both data and target availability when offline."""
14+
config = Config(sdk_key="sdk-key", offline=True)
15+
fdv1 = FDv1(config)
16+
17+
assert fdv1.data_availability == DataAvailability.DEFAULTS
18+
assert fdv1.target_availability == DataAvailability.DEFAULTS
19+
20+
21+
def test_fdv1_availability_ldd_mode_uninitialized():
22+
"""Test that FDv1 returns DEFAULTS for data and CACHED for target when LDD mode with uninitialized store."""
23+
store = InMemoryFeatureStore()
24+
config = Config(sdk_key="sdk-key", use_ldd=True, feature_store=store)
25+
fdv1 = FDv1(config)
26+
27+
# Store is not initialized yet
28+
assert not store.initialized
29+
assert fdv1.data_availability == DataAvailability.DEFAULTS
30+
assert fdv1.target_availability == DataAvailability.CACHED
31+
32+
33+
def test_fdv1_availability_ldd_mode_initialized():
34+
"""Test that FDv1 returns CACHED for both when LDD mode with initialized store."""
35+
store = InMemoryFeatureStore()
36+
config = Config(sdk_key="sdk-key", use_ldd=True, feature_store=store)
37+
fdv1 = FDv1(config)
38+
39+
# Initialize the store
40+
store.init({FEATURES: {}})
41+
42+
assert store.initialized
43+
assert fdv1.data_availability == DataAvailability.CACHED
44+
assert fdv1.target_availability == DataAvailability.CACHED
45+
46+
47+
def test_fdv1_availability_normal_mode_uninitialized():
48+
"""Test that FDv1 returns DEFAULTS for data and REFRESHED for target in normal mode when not initialized."""
49+
store = InMemoryFeatureStore()
50+
config = Config(sdk_key="sdk-key", feature_store=store)
51+
fdv1 = FDv1(config)
52+
53+
# Update processor not started, store not initialized
54+
assert fdv1.data_availability == DataAvailability.DEFAULTS
55+
assert fdv1.target_availability == DataAvailability.REFRESHED
56+
57+
58+
def test_fdv1_availability_normal_mode_store_initialized():
59+
"""Test that FDv1 returns CACHED for data and REFRESHED for target when store is initialized but update processor is not."""
60+
store = InMemoryFeatureStore()
61+
config = Config(sdk_key="sdk-key", feature_store=store)
62+
fdv1 = FDv1(config)
63+
64+
# Initialize store but don't start update processor
65+
fdv1._store_wrapper.init({FEATURES: {}})
66+
67+
assert fdv1.data_availability == DataAvailability.CACHED
68+
assert fdv1.target_availability == DataAvailability.REFRESHED

ldclient/testing/impl/datasystem/test_fdv2_datasystem.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,3 +545,162 @@ def listener(_: FlagChange):
545545
fdv2.stop()
546546
finally:
547547
os.remove(path)
548+
549+
550+
def test_fdv2_availability_without_data_sources():
551+
"""Test that FDv2 returns DEFAULTS for both data and target availability when not configured with data sources or a fallback data store."""
552+
# No initializers, no primary synchronizer - not configured with data sources
553+
data_system_config = DataSystemConfig(
554+
initializers=None,
555+
primary_synchronizer=None,
556+
)
557+
558+
fdv2 = FDv2(Config(sdk_key="dummy"), data_system_config)
559+
560+
# Should return CACHED since not configured with data sources
561+
assert fdv2.data_availability == DataAvailability.DEFAULTS
562+
assert fdv2.target_availability == DataAvailability.DEFAULTS
563+
564+
565+
def test_fdv2_availability_offline():
566+
"""Test that FDv2 returns DEFAULTS for target availability and data availability when offline."""
567+
data_system_config = DataSystemConfig(
568+
initializers=None,
569+
primary_synchronizer=None,
570+
)
571+
572+
fdv2 = FDv2(Config(sdk_key="dummy", offline=True), data_system_config)
573+
574+
assert fdv2.data_availability == DataAvailability.DEFAULTS
575+
assert fdv2.target_availability == DataAvailability.DEFAULTS
576+
577+
578+
def test_fdv2_availability_with_data_sources_no_store():
579+
"""Test that FDv2 returns DEFAULTS for data and REFRESHED for target when configured with data sources but no store and uninitialized."""
580+
td = TestDataV2.data_source()
581+
582+
data_system_config = DataSystemConfig(
583+
initializers=None,
584+
primary_synchronizer=td.build_synchronizer,
585+
)
586+
587+
fdv2 = FDv2(Config(sdk_key="dummy"), data_system_config)
588+
589+
# Store is not initialized, and we have data sources configured
590+
assert not fdv2._store.is_initialized()
591+
assert fdv2.data_availability == DataAvailability.DEFAULTS
592+
assert fdv2.target_availability == DataAvailability.REFRESHED
593+
594+
595+
def test_fdv2_availability_no_data_sources_with_readonly_store_uninitialized():
596+
"""Test that FDv2 returns DEFAULTS for both when no data sources and read-only store is uninitialized."""
597+
from ldclient.testing.impl.datasystem.test_fdv2_persistence import StubFeatureStore
598+
from ldclient.interfaces import DataStoreMode
599+
600+
store = StubFeatureStore()
601+
data_system_config = DataSystemConfig(
602+
initializers=None,
603+
primary_synchronizer=None,
604+
data_store=store,
605+
data_store_mode=DataStoreMode.READ_ONLY,
606+
)
607+
608+
fdv2 = FDv2(Config(sdk_key="dummy"), data_system_config)
609+
610+
# Store is not initialized
611+
assert not store.initialized
612+
assert fdv2.data_availability == DataAvailability.DEFAULTS
613+
assert fdv2.target_availability == DataAvailability.CACHED
614+
615+
616+
def test_fdv2_availability_no_data_sources_with_readonly_store_initialized():
617+
"""Test that FDv2 returns CACHED for both when no data sources and read-only store is initialized."""
618+
from ldclient.testing.impl.datasystem.test_fdv2_persistence import StubFeatureStore
619+
from ldclient.interfaces import DataStoreMode
620+
621+
store = StubFeatureStore()
622+
store.init({FEATURES: {}})
623+
624+
data_system_config = DataSystemConfig(
625+
initializers=None,
626+
primary_synchronizer=None,
627+
data_store=store,
628+
data_store_mode=DataStoreMode.READ_ONLY,
629+
)
630+
631+
fdv2 = FDv2(Config(sdk_key="dummy"), data_system_config)
632+
633+
# Store is initialized
634+
assert store.initialized
635+
assert fdv2.data_availability == DataAvailability.CACHED
636+
assert fdv2.target_availability == DataAvailability.CACHED
637+
638+
639+
def test_fdv2_availability_no_data_sources_with_readwrite_store_initialized():
640+
"""Test that FDv2 returns CACHED for both when no data sources and read-write store is initialized."""
641+
from ldclient.testing.impl.datasystem.test_fdv2_persistence import StubFeatureStore
642+
from ldclient.interfaces import DataStoreMode
643+
644+
store = StubFeatureStore()
645+
store.init({FEATURES: {}})
646+
647+
data_system_config = DataSystemConfig(
648+
initializers=None,
649+
primary_synchronizer=None,
650+
data_store=store,
651+
data_store_mode=DataStoreMode.READ_WRITE,
652+
)
653+
654+
fdv2 = FDv2(Config(sdk_key="dummy"), data_system_config)
655+
656+
# Store is initialized
657+
assert store.initialized
658+
assert fdv2.data_availability == DataAvailability.CACHED
659+
assert fdv2.target_availability == DataAvailability.CACHED
660+
661+
662+
def test_fdv2_availability_with_data_sources_and_store_uninitialized():
663+
"""Test that FDv2 returns DEFAULTS for data and REFRESHED for target when data sources configured with uninitialized store."""
664+
from ldclient.testing.impl.datasystem.test_fdv2_persistence import StubFeatureStore
665+
from ldclient.interfaces import DataStoreMode
666+
667+
td = TestDataV2.data_source()
668+
store = StubFeatureStore()
669+
670+
data_system_config = DataSystemConfig(
671+
initializers=None,
672+
primary_synchronizer=td.build_synchronizer,
673+
data_store=store,
674+
data_store_mode=DataStoreMode.READ_WRITE,
675+
)
676+
677+
fdv2 = FDv2(Config(sdk_key="dummy"), data_system_config)
678+
679+
# Store is not initialized
680+
assert not store.initialized
681+
assert fdv2.data_availability == DataAvailability.DEFAULTS
682+
assert fdv2.target_availability == DataAvailability.REFRESHED
683+
684+
685+
def test_fdv2_availability_with_data_sources_and_store_initialized():
686+
"""Test that FDv2 returns CACHED for data and REFRESHED for target when data sources configured with initialized store."""
687+
from ldclient.testing.impl.datasystem.test_fdv2_persistence import StubFeatureStore
688+
from ldclient.interfaces import DataStoreMode
689+
690+
td = TestDataV2.data_source()
691+
store = StubFeatureStore()
692+
store.init({FEATURES: {}})
693+
694+
data_system_config = DataSystemConfig(
695+
initializers=None,
696+
primary_synchronizer=td.build_synchronizer,
697+
data_store=store,
698+
data_store_mode=DataStoreMode.READ_WRITE,
699+
)
700+
701+
fdv2 = FDv2(Config(sdk_key="dummy"), data_system_config)
702+
703+
# Store is initialized but selector not defined yet (synchronizer not started)
704+
assert store.initialized
705+
assert fdv2.data_availability == DataAvailability.CACHED
706+
assert fdv2.target_availability == DataAvailability.REFRESHED

0 commit comments

Comments
 (0)