From 892cb7877eebc3d1b612b205718418cc405498ae Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Tue, 3 Feb 2026 22:26:44 +0000 Subject: [PATCH] feat: Update ChangeSet to always require a Selector --- ldclient/impl/datasourcev2/polling.py | 2 +- ldclient/impl/datasystem/fdv2.py | 2 +- ldclient/impl/datasystem/store.py | 4 ++-- ldclient/interfaces.py | 2 +- .../impl/datasourcev2/test_polling_payload_parsing.py | 8 +++----- .../impl/datasourcev2/test_polling_synchronizer.py | 6 +++--- .../impl/datasourcev2/test_streaming_synchronizer.py | 10 +++++----- 7 files changed, 16 insertions(+), 18 deletions(-) diff --git a/ldclient/impl/datasourcev2/polling.py b/ldclient/impl/datasourcev2/polling.py index 2e579d9b..724b6871 100644 --- a/ldclient/impl/datasourcev2/polling.py +++ b/ldclient/impl/datasourcev2/polling.py @@ -221,7 +221,7 @@ def _poll(self, ss: SelectorStore) -> BasisResult: basis = Basis( change_set=change_set, - persist=change_set.selector is not None and change_set.selector.is_defined(), + persist=change_set.selector.is_defined(), environment_id=env_id, ) diff --git a/ldclient/impl/datasystem/fdv2.py b/ldclient/impl/datasystem/fdv2.py index 3e2864c8..d5ead2b1 100644 --- a/ldclient/impl/datasystem/fdv2.py +++ b/ldclient/impl/datasystem/fdv2.py @@ -414,7 +414,7 @@ def _run_initializers(self, set_on_ready: Event): self._store.apply(basis.change_set, basis.persist) # Set ready event if an only if a selector is defined for the changeset - if basis.change_set.selector is not None and basis.change_set.selector.is_defined(): + if basis.change_set.selector.is_defined(): set_on_ready.set() return except Exception as e: diff --git a/ldclient/impl/datasystem/store.py b/ldclient/impl/datasystem/store.py index 2e554915..52c9bf20 100644 --- a/ldclient/impl/datasystem/store.py +++ b/ldclient/impl/datasystem/store.py @@ -268,7 +268,7 @@ def apply(self, change_set: ChangeSet, persist: bool) -> None: log.error("Store: couldn't apply changeset: %s", str(e)) def _set_basis( - self, collections: Collections, selector: Optional[Selector], persist: bool + self, collections: Collections, selector: Selector, persist: bool ) -> None: """ Set the basis of the store. Any existing data is discarded. @@ -310,7 +310,7 @@ def _set_basis( self._send_change_events(affected_items) def _apply_delta( - self, collections: Collections, selector: Optional[Selector], persist: bool + self, collections: Collections, selector: Selector, persist: bool ) -> None: """ Apply a delta update to the store. diff --git a/ldclient/interfaces.py b/ldclient/interfaces.py index f1a1e550..62e20527 100644 --- a/ldclient/interfaces.py +++ b/ldclient/interfaces.py @@ -1346,7 +1346,7 @@ class ChangeSet: intent_code: IntentCode changes: List[Change] - selector: Optional[Selector] + selector: Selector @dataclass(frozen=True) diff --git a/ldclient/testing/impl/datasourcev2/test_polling_payload_parsing.py b/ldclient/testing/impl/datasourcev2/test_polling_payload_parsing.py index 56c6f9eb..11fd2d68 100644 --- a/ldclient/testing/impl/datasourcev2/test_polling_payload_parsing.py +++ b/ldclient/testing/impl/datasourcev2/test_polling_payload_parsing.py @@ -46,7 +46,6 @@ def test_transfer_none(): change_set = result.value assert change_set.intent_code == IntentCode.TRANSFER_NONE assert len(change_set.changes) == 0 - assert change_set.selector is not None assert not change_set.selector.is_defined() @@ -59,7 +58,7 @@ def test_transfer_full_with_empty_payload(): change_set = result.value assert change_set.intent_code == IntentCode.TRANSFER_FULL assert len(change_set.changes) == 0 - assert change_set.selector is not None + assert change_set.selector.is_defined() assert change_set.selector.state == "(p:5A46PZ79FQ9D08YYKT79DECDNV:461)" assert change_set.selector.version == 461 @@ -87,7 +86,7 @@ def test_processes_put_object(): assert change_set.changes[0].version == 461 assert isinstance(change_set.changes[0].object, dict) - assert change_set.selector is not None + assert change_set.selector.is_defined() assert change_set.selector.state == "(p:5A46PZ79FQ9D08YYKT79DECDNV:461)" assert change_set.selector.version == 461 @@ -107,7 +106,7 @@ def test_processes_delete_object(): assert change_set.changes[0].version == 461 assert change_set.changes[0].object is None - assert change_set.selector is not None + assert change_set.selector.is_defined() assert change_set.selector.state == "(p:5A46PZ79FQ9D08YYKT79DECDNV:461)" assert change_set.selector.version == 461 @@ -169,7 +168,6 @@ def test_fdv1_payload_empty_flags_and_segments(): assert change_set.intent_code == IntentCode.TRANSFER_FULL assert len(change_set.changes) == 0 # FDv1 doesn't use selectors - assert change_set.selector is not None assert not change_set.selector.is_defined() diff --git a/ldclient/testing/impl/datasourcev2/test_polling_synchronizer.py b/ldclient/testing/impl/datasourcev2/test_polling_synchronizer.py index ebb2674a..7bc1d79d 100644 --- a/ldclient/testing/impl/datasourcev2/test_polling_synchronizer.py +++ b/ldclient/testing/impl/datasourcev2/test_polling_synchronizer.py @@ -142,7 +142,7 @@ def test_handles_empty_changeset(): assert valid.change_set is not None assert len(valid.change_set.changes) == 0 - assert valid.change_set.selector is not None + assert valid.change_set.selector.is_defined() assert valid.change_set.selector.version == 300 assert valid.change_set.selector.state == "p:SOMETHING:300" assert valid.change_set.intent_code == IntentCode.TRANSFER_FULL @@ -175,7 +175,7 @@ def test_handles_put_objects(): assert valid.change_set.changes[0].key == "flag-key" assert valid.change_set.changes[0].object == {"key": "flag-key"} assert valid.change_set.changes[0].version == 100 - assert valid.change_set.selector is not None + assert valid.change_set.selector.is_defined() assert valid.change_set.selector.version == 300 assert valid.change_set.selector.state == "p:SOMETHING:300" assert valid.change_set.intent_code == IntentCode.TRANSFER_FULL @@ -205,7 +205,7 @@ def test_handles_delete_objects(): assert valid.change_set.changes[0].kind == ObjectKind.FLAG assert valid.change_set.changes[0].key == "flag-key" assert valid.change_set.changes[0].version == 101 - assert valid.change_set.selector is not None + assert valid.change_set.selector.is_defined() assert valid.change_set.selector.version == 300 assert valid.change_set.selector.state == "p:SOMETHING:300" assert valid.change_set.intent_code == IntentCode.TRANSFER_FULL diff --git a/ldclient/testing/impl/datasourcev2/test_streaming_synchronizer.py b/ldclient/testing/impl/datasourcev2/test_streaming_synchronizer.py index b91d5fba..aff156c8 100644 --- a/ldclient/testing/impl/datasourcev2/test_streaming_synchronizer.py +++ b/ldclient/testing/impl/datasourcev2/test_streaming_synchronizer.py @@ -209,7 +209,7 @@ def test_handles_empty_changeset(events): # pylint: disable=redefined-outer-nam assert updates[0].change_set is not None assert len(updates[0].change_set.changes) == 0 - assert updates[0].change_set.selector is not None + assert updates[0].change_set.selector.is_defined() assert updates[0].change_set.selector.version == 300 assert updates[0].change_set.selector.state == "p:SOMETHING:300" assert updates[0].change_set.intent_code == IntentCode.TRANSFER_FULL @@ -241,7 +241,7 @@ def test_handles_put_objects(events): # pylint: disable=redefined-outer-name assert updates[0].change_set.changes[0].key == "flag-key" assert updates[0].change_set.changes[0].object == {"key": "flag-key"} assert updates[0].change_set.changes[0].version == 100 - assert updates[0].change_set.selector is not None + assert updates[0].change_set.selector.is_defined() assert updates[0].change_set.selector.version == 300 assert updates[0].change_set.selector.state == "p:SOMETHING:300" assert updates[0].change_set.intent_code == IntentCode.TRANSFER_FULL @@ -272,7 +272,7 @@ def test_handles_delete_objects(events): # pylint: disable=redefined-outer-name assert updates[0].change_set.changes[0].kind == ObjectKind.FLAG assert updates[0].change_set.changes[0].key == "flag-key" assert updates[0].change_set.changes[0].version == 101 - assert updates[0].change_set.selector is not None + assert updates[0].change_set.selector.is_defined() assert updates[0].change_set.selector.version == 300 assert updates[0].change_set.selector.state == "p:SOMETHING:300" assert updates[0].change_set.intent_code == IntentCode.TRANSFER_FULL @@ -299,7 +299,7 @@ def test_swallows_goodbye(events): # pylint: disable=redefined-outer-name assert updates[0].change_set is not None assert len(updates[0].change_set.changes) == 0 - assert updates[0].change_set.selector is not None + assert updates[0].change_set.selector.is_defined() assert updates[0].change_set.selector.version == 300 assert updates[0].change_set.selector.state == "p:SOMETHING:300" assert updates[0].change_set.intent_code == IntentCode.TRANSFER_FULL @@ -326,7 +326,7 @@ def test_swallows_heartbeat(events): # pylint: disable=redefined-outer-name assert updates[0].change_set is not None assert len(updates[0].change_set.changes) == 0 - assert updates[0].change_set.selector is not None + assert updates[0].change_set.selector.is_defined() assert updates[0].change_set.selector.version == 300 assert updates[0].change_set.selector.state == "p:SOMETHING:300" assert updates[0].change_set.intent_code == IntentCode.TRANSFER_FULL