Skip to content

Commit 1c998cd

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

File tree

4 files changed

+238
-4
lines changed

4 files changed

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

0 commit comments

Comments
 (0)