Skip to content

Commit efcf0cb

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

File tree

4 files changed

+253
-4
lines changed

4 files changed

+253
-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: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,3 +545,172 @@ 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.interfaces import DataStoreMode
598+
from ldclient.testing.impl.datasystem.test_fdv2_persistence import (
599+
StubFeatureStore
600+
)
601+
602+
store = StubFeatureStore()
603+
data_system_config = DataSystemConfig(
604+
initializers=None,
605+
primary_synchronizer=None,
606+
data_store=store,
607+
data_store_mode=DataStoreMode.READ_ONLY,
608+
)
609+
610+
fdv2 = FDv2(Config(sdk_key="dummy"), data_system_config)
611+
612+
# Store is not initialized
613+
assert not store.initialized
614+
assert fdv2.data_availability == DataAvailability.DEFAULTS
615+
assert fdv2.target_availability == DataAvailability.CACHED
616+
617+
618+
def test_fdv2_availability_no_data_sources_with_readonly_store_initialized():
619+
"""Test that FDv2 returns CACHED for both when no data sources and read-only store is initialized."""
620+
from ldclient.interfaces import DataStoreMode
621+
from ldclient.testing.impl.datasystem.test_fdv2_persistence import (
622+
StubFeatureStore
623+
)
624+
625+
store = StubFeatureStore()
626+
store.init({FEATURES: {}})
627+
628+
data_system_config = DataSystemConfig(
629+
initializers=None,
630+
primary_synchronizer=None,
631+
data_store=store,
632+
data_store_mode=DataStoreMode.READ_ONLY,
633+
)
634+
635+
fdv2 = FDv2(Config(sdk_key="dummy"), data_system_config)
636+
637+
# Store is initialized
638+
assert store.initialized
639+
assert fdv2.data_availability == DataAvailability.CACHED
640+
assert fdv2.target_availability == DataAvailability.CACHED
641+
642+
643+
def test_fdv2_availability_no_data_sources_with_readwrite_store_initialized():
644+
"""Test that FDv2 returns CACHED for both when no data sources and read-write store is initialized."""
645+
from ldclient.interfaces import DataStoreMode
646+
from ldclient.testing.impl.datasystem.test_fdv2_persistence import (
647+
StubFeatureStore
648+
)
649+
650+
store = StubFeatureStore()
651+
store.init({FEATURES: {}})
652+
653+
data_system_config = DataSystemConfig(
654+
initializers=None,
655+
primary_synchronizer=None,
656+
data_store=store,
657+
data_store_mode=DataStoreMode.READ_WRITE,
658+
)
659+
660+
fdv2 = FDv2(Config(sdk_key="dummy"), data_system_config)
661+
662+
# Store is initialized
663+
assert store.initialized
664+
assert fdv2.data_availability == DataAvailability.CACHED
665+
assert fdv2.target_availability == DataAvailability.CACHED
666+
667+
668+
def test_fdv2_availability_with_data_sources_and_store_uninitialized():
669+
"""Test that FDv2 returns DEFAULTS for data and REFRESHED for target when data sources configured with uninitialized store."""
670+
from ldclient.interfaces import DataStoreMode
671+
from ldclient.testing.impl.datasystem.test_fdv2_persistence import (
672+
StubFeatureStore
673+
)
674+
675+
td = TestDataV2.data_source()
676+
store = StubFeatureStore()
677+
678+
data_system_config = DataSystemConfig(
679+
initializers=None,
680+
primary_synchronizer=td.build_synchronizer,
681+
data_store=store,
682+
data_store_mode=DataStoreMode.READ_WRITE,
683+
)
684+
685+
fdv2 = FDv2(Config(sdk_key="dummy"), data_system_config)
686+
687+
# Store is not initialized
688+
assert not store.initialized
689+
assert fdv2.data_availability == DataAvailability.DEFAULTS
690+
assert fdv2.target_availability == DataAvailability.REFRESHED
691+
692+
693+
def test_fdv2_availability_with_data_sources_and_store_initialized():
694+
"""Test that FDv2 returns CACHED for data and REFRESHED for target when data sources configured with initialized store."""
695+
from ldclient.interfaces import DataStoreMode
696+
from ldclient.testing.impl.datasystem.test_fdv2_persistence import (
697+
StubFeatureStore
698+
)
699+
700+
td = TestDataV2.data_source()
701+
store = StubFeatureStore()
702+
store.init({FEATURES: {}})
703+
704+
data_system_config = DataSystemConfig(
705+
initializers=None,
706+
primary_synchronizer=td.build_synchronizer,
707+
data_store=store,
708+
data_store_mode=DataStoreMode.READ_WRITE,
709+
)
710+
711+
fdv2 = FDv2(Config(sdk_key="dummy"), data_system_config)
712+
713+
# Store is initialized but selector not defined yet (synchronizer not started)
714+
assert store.initialized
715+
assert fdv2.data_availability == DataAvailability.CACHED
716+
assert fdv2.target_availability == DataAvailability.REFRESHED

0 commit comments

Comments
 (0)