From e7835217fb0e10475c82952a67443b13d22f5c96 Mon Sep 17 00:00:00 2001 From: Andrew Shao Date: Thu, 20 Aug 2026 18:08:34 -0700 Subject: [PATCH 1/4] Bubble up delete functionality --- include/radex/client_base.hpp | 9 ++++++++ include/radex/dragon.hpp | 1 + include/radex/smartredis.hpp | 1 + src/cpp/client.cpp | 7 ++++++ src/cpp/dragon.cpp | 5 +++++ src/cpp/smartredis.cpp | 4 ++++ src/python/src/radex/clients/core.pxd | 2 ++ src/python/src/radex/clients/core.pyx | 9 ++++++++ tests/cpp/test_local_client.cpp | 32 +++++++++++++++++++++++++++ tests/python/dragon/test_delete.py | 25 +++++++++++++++++++++ 10 files changed, 95 insertions(+) create mode 100644 tests/python/dragon/test_delete.py diff --git a/include/radex/client_base.hpp b/include/radex/client_base.hpp index b76d471..fe7ef6c 100644 --- a/include/radex/client_base.hpp +++ b/include/radex/client_base.hpp @@ -186,6 +186,8 @@ class IClient { /// @return True if `key` exists in the backing store. virtual bool contains(std::string_view key) = 0; + /// Delete the entry stored under `key`. + virtual void delete_key(std::string_view key) = 0; /// Store raw bytes under `key`, overwriting any existing value. virtual void put_bytes(std::string_view key, const void *bytes, detail::MetaInt length) = 0; @@ -213,6 +215,9 @@ class IClient { wait_for_item_info_ptr(const data::IncomingHandle &handle, std::chrono::milliseconds timeout); + /// Delete a typed value and its associated metadata. + void delete_item(const data::IncomingHandle &handle); + public: /// Store a scalar value under `handle`. /// @tparam T One of `int32_t`, `int64_t`, `float`, `double`. @@ -363,6 +368,10 @@ class Client : public IClient { throw_backend_unavailable(); } + void delete_key(std::string_view key) override { + throw_backend_unavailable(); + } + void put_bytes(std::string_view key, const void *bytes, detail::MetaInt length) override { throw_backend_unavailable(); } diff --git a/include/radex/dragon.hpp b/include/radex/dragon.hpp index 0e2e690..ea3b4b0 100644 --- a/include/radex/dragon.hpp +++ b/include/radex/dragon.hpp @@ -37,6 +37,7 @@ class Client : public IClient { ~Client() = default; bool contains(std::string_view key) override; + void delete_key(std::string_view key) override; detail::BytesBuffer wait_for_bytes(std::string_view key, std::chrono::milliseconds timeout) override; void put_bytes(std::string_view key, const void *bytes, diff --git a/include/radex/smartredis.hpp b/include/radex/smartredis.hpp index 4c7a180..52f01c1 100644 --- a/include/radex/smartredis.hpp +++ b/include/radex/smartredis.hpp @@ -33,6 +33,7 @@ class Client : public IClient { ~Client() = default; bool contains(std::string_view key) override; + void delete_key(std::string_view key) override; void put_bytes(std::string_view key, const void *bytes, detail::MetaInt length) override; radex::detail::BytesBuffer get_bytes(std::string_view key) override; diff --git a/src/cpp/client.cpp b/src/cpp/client.cpp index 830281a..f283b24 100644 --- a/src/cpp/client.cpp +++ b/src/cpp/client.cpp @@ -105,6 +105,13 @@ IClient::wait_for_item_info_ptr(const data::IncomingHandle &handle, get_item_info(fetch_bytes, handle)); } +void IClient::delete_item(const data::IncomingHandle &handle) { + delete_key(handle.key()); + if (contains(handle.metadata_key())) { + delete_key(handle.metadata_key()); + } +} + detail::MetaData IClient::get_meta_data(const data::IncomingHandle &handle) { return detail::MetaData::from_buffer(get_bytes(handle.metadata_key())); } diff --git a/src/cpp/dragon.cpp b/src/cpp/dragon.cpp index a76e718..81a07f5 100644 --- a/src/cpp/dragon.cpp +++ b/src/cpp/dragon.cpp @@ -67,6 +67,11 @@ bool Client::contains(std::string_view key) { return ddict.contains(key_); } +void Client::delete_key(std::string_view key) { + dragon::SerializableString key_{std::string{key}}; + ddict.erase(key_); +} + void Client::put_bytes(std::string_view key, const void *bytes, detail::MetaInt length) { // FIXME: Gross const cast needed -- check with Kent\!\! diff --git a/src/cpp/smartredis.cpp b/src/cpp/smartredis.cpp index 41a785a..9889213 100644 --- a/src/cpp/smartredis.cpp +++ b/src/cpp/smartredis.cpp @@ -63,6 +63,10 @@ bool Client::contains(std::string_view key) { return client.key_exists(std::string{key}); } +void Client::delete_key(std::string_view key) { + client.delete_key(std::string{key}); +} + void Client::put_bytes(std::string_view key, const void *bytes, detail::MetaInt length) { client.put_bytes(std::string{key}, bytes, length); diff --git a/src/python/src/radex/clients/core.pxd b/src/python/src/radex/clients/core.pxd index 9ca37d9..8b6e51a 100644 --- a/src/python/src/radex/clients/core.pxd +++ b/src/python/src/radex/clients/core.pxd @@ -25,6 +25,7 @@ cdef extern from "radex/client.hpp" namespace "radex": void put_bytes(string_view, const void*, size_t) except +raise_py_error BytesBuffer get_bytes(string_view) except +raise_py_error BytesBuffer wait_for_bytes(string_view, milliseconds) except +raise_py_error + void delete_key(string_view) except + # <<< End Virtual Methods <<< void put_scalar[T](const OutgoingHandle&, T) except +raise_py_error @@ -35,3 +36,4 @@ cdef extern from "radex/client.hpp" namespace "radex": unique_ptr[ItemInfo] get_item_info_ptr(const IncomingHandle&) except +raise_py_error unique_ptr[ItemInfo] wait_for_item_info_ptr( const IncomingHandle&, milliseconds) except +raise_py_error + void delete_item(const IncomingHandle&) except +raise_py_error diff --git a/src/python/src/radex/clients/core.pyx b/src/python/src/radex/clients/core.pyx index 4f2c606..cce10c9 100644 --- a/src/python/src/radex/clients/core.pyx +++ b/src/python/src/radex/clients/core.pyx @@ -65,6 +65,15 @@ cdef class PyClient: cdef np.number val = coerce_py_objects_to_np_numbers(value) return self._put_scalar(handle, val) + def delete_key(self, str key): + """Delete a raw entry from the backing store.""" + cdef string key_ = encode_str(key) + self._client.delete_key(key_.c_str()) + + def delete_item(self, IncomingHandle handle): + """Delete a typed value and its associated metadata.""" + self._client.delete_item(handle.unwrap()[0]) + def _put_scalar(self, OutgoingHandle handle, np.number value not None): # FIXME: Get rid of this ugly swith statment. Ideally we could used the # fused `SupportedType` type, but there seems to be a known diff --git a/tests/cpp/test_local_client.cpp b/tests/cpp/test_local_client.cpp index abc8a48..1eaab3d 100644 --- a/tests/cpp/test_local_client.cpp +++ b/tests/cpp/test_local_client.cpp @@ -27,6 +27,10 @@ class UnorderedMapClient : public radex::IClient { return true; } + void delete_key(std::string_view key) override { + _map.erase(std::string{key}); + } + void put_bytes(std::string_view key, const void *bytes, radex::detail::MetaInt length) override { auto ptr = static_cast(bytes); @@ -75,6 +79,34 @@ TEMPLATE_TEST_CASE("In memory client test cases", "[in-mem]", std::int32_t, } } + SECTION("Client can delete a scalar value and its metadata") { + const radex::data::OutgoingHandle outgoing{"my-scalar"}; + const radex::data::IncomingHandle incoming{"my-scalar"}; + client.put_scalar(outgoing, TestType{}); + REQUIRE(client.contains(outgoing.key())); + REQUIRE(client.contains(outgoing.metadata_key())); + + client.delete_item(incoming); + + REQUIRE_FALSE(client.contains(outgoing.key())); + REQUIRE_FALSE(client.contains(outgoing.metadata_key())); + } + + SECTION("Client can delete a tensor value and its metadata") { + const radex::data::OutgoingHandle outgoing{"my-tensor-to-delete"}; + const radex::data::IncomingHandle incoming{"my-tensor-to-delete"}; + const std::vector dims{2}; + const std::vector data{TestType{1}, TestType{2}}; + client.put_tensor(outgoing, dims, data); + REQUIRE(client.contains(outgoing.key())); + REQUIRE(client.contains(outgoing.metadata_key())); + + client.delete_item(incoming); + + REQUIRE_FALSE(client.contains(outgoing.key())); + REQUIRE_FALSE(client.contains(outgoing.metadata_key())); + } + SECTION("Client can put and get a 1D tensor value") { const int size = 12; std::vector x_data(size); diff --git a/tests/python/dragon/test_delete.py b/tests/python/dragon/test_delete.py new file mode 100644 index 0000000..94c2517 --- /dev/null +++ b/tests/python/dragon/test_delete.py @@ -0,0 +1,25 @@ +import pytest + +from radex.handles.handles import IncomingHandle, OutgoingHandle + + +def test_delete_typed_item(client, random_np_value): + key = "some-value-to-delete" + client.put_scalar(OutgoingHandle(key), random_np_value) + assert client.contains(key) + + client.delete_item(IncomingHandle(key)) + + assert not client.contains(key) + with pytest.raises(Exception): + client.get_scalar(IncomingHandle(key)) + + +def test_delete_raw_key(client, random_picklable): + key = "some-object-to-delete" + client.put_picklable(key, random_picklable) + assert client.contains(key) + + client.delete_key(key) + + assert not client.contains(key) From bd4ff12dce5a05cca7ce0efb838def70c4259ab4 Mon Sep 17 00:00:00 2001 From: Andrew Shao Date: Fri, 21 Aug 2026 06:54:32 -0700 Subject: [PATCH 2/4] Make delete_keys and other methods private --- example/cpp-exchange/in-mem/proof_of_concept_iface.cpp | 6 ++++++ include/radex/client_base.hpp | 6 ++++-- include/radex/dragon.hpp | 3 ++- include/radex/smartredis.hpp | 3 ++- src/cpp/smartredis.cpp | 2 +- src/python/src/radex/clients/core.pyx | 5 ----- tests/cpp/test_local_client.cpp | 4 +++- tests/python/dragon/test_delete.py | 2 +- 8 files changed, 19 insertions(+), 12 deletions(-) diff --git a/example/cpp-exchange/in-mem/proof_of_concept_iface.cpp b/example/cpp-exchange/in-mem/proof_of_concept_iface.cpp index 9654045..2687375 100644 --- a/example/cpp-exchange/in-mem/proof_of_concept_iface.cpp +++ b/example/cpp-exchange/in-mem/proof_of_concept_iface.cpp @@ -28,6 +28,12 @@ class UnorderedMapClient : public radex::IClient { return true; } + private: + void delete_key(std::string_view key) override { + _map.erase(std::string{key}); + } + + public: void put_bytes(std::string_view key, const void *bytes, radex::detail::MetaInt length) override { auto ptr = static_cast(bytes); diff --git a/include/radex/client_base.hpp b/include/radex/client_base.hpp index fe7ef6c..04bc1c5 100644 --- a/include/radex/client_base.hpp +++ b/include/radex/client_base.hpp @@ -186,8 +186,6 @@ class IClient { /// @return True if `key` exists in the backing store. virtual bool contains(std::string_view key) = 0; - /// Delete the entry stored under `key`. - virtual void delete_key(std::string_view key) = 0; /// Store raw bytes under `key`, overwriting any existing value. virtual void put_bytes(std::string_view key, const void *bytes, detail::MetaInt length) = 0; @@ -203,6 +201,9 @@ class IClient { // <<< End Virtual Methods <<< private: + /// Delete the entry stored under `key` in the backing store. + virtual void delete_key(std::string_view key) = 0; + detail::ItemInfo get_item_info( std::function fetch_bytes, const data::IncomingHandle &handle); @@ -368,6 +369,7 @@ class Client : public IClient { throw_backend_unavailable(); } + private: void delete_key(std::string_view key) override { throw_backend_unavailable(); } diff --git a/include/radex/dragon.hpp b/include/radex/dragon.hpp index ea3b4b0..4278f98 100644 --- a/include/radex/dragon.hpp +++ b/include/radex/dragon.hpp @@ -21,6 +21,8 @@ class Client : public IClient { Client(dragon::DDict ddict); + void delete_key(std::string_view key) override; + bool dormant_timeout_warning_triggered = false; public: @@ -37,7 +39,6 @@ class Client : public IClient { ~Client() = default; bool contains(std::string_view key) override; - void delete_key(std::string_view key) override; detail::BytesBuffer wait_for_bytes(std::string_view key, std::chrono::milliseconds timeout) override; void put_bytes(std::string_view key, const void *bytes, diff --git a/include/radex/smartredis.hpp b/include/radex/smartredis.hpp index 52f01c1..f1597f9 100644 --- a/include/radex/smartredis.hpp +++ b/include/radex/smartredis.hpp @@ -20,6 +20,8 @@ class Client : public IClient { Client(std::unique_ptr options, std::string_view logger_name); + void delete_key(std::string_view key) override; + public: /// Connect using configuration read from the environment. Client(); @@ -33,7 +35,6 @@ class Client : public IClient { ~Client() = default; bool contains(std::string_view key) override; - void delete_key(std::string_view key) override; void put_bytes(std::string_view key, const void *bytes, detail::MetaInt length) override; radex::detail::BytesBuffer get_bytes(std::string_view key) override; diff --git a/src/cpp/smartredis.cpp b/src/cpp/smartredis.cpp index 9889213..519abc4 100644 --- a/src/cpp/smartredis.cpp +++ b/src/cpp/smartredis.cpp @@ -64,7 +64,7 @@ bool Client::contains(std::string_view key) { } void Client::delete_key(std::string_view key) { - client.delete_key(std::string{key}); + client.delete_bytes(std::string{key}); } void Client::put_bytes(std::string_view key, const void *bytes, diff --git a/src/python/src/radex/clients/core.pyx b/src/python/src/radex/clients/core.pyx index cce10c9..424dd4d 100644 --- a/src/python/src/radex/clients/core.pyx +++ b/src/python/src/radex/clients/core.pyx @@ -65,11 +65,6 @@ cdef class PyClient: cdef np.number val = coerce_py_objects_to_np_numbers(value) return self._put_scalar(handle, val) - def delete_key(self, str key): - """Delete a raw entry from the backing store.""" - cdef string key_ = encode_str(key) - self._client.delete_key(key_.c_str()) - def delete_item(self, IncomingHandle handle): """Delete a typed value and its associated metadata.""" self._client.delete_item(handle.unwrap()[0]) diff --git a/tests/cpp/test_local_client.cpp b/tests/cpp/test_local_client.cpp index 1eaab3d..e23e39a 100644 --- a/tests/cpp/test_local_client.cpp +++ b/tests/cpp/test_local_client.cpp @@ -27,10 +27,12 @@ class UnorderedMapClient : public radex::IClient { return true; } - void delete_key(std::string_view key) override { + private: + void delete_key(std::string_view key) override { _map.erase(std::string{key}); } + public: void put_bytes(std::string_view key, const void *bytes, radex::detail::MetaInt length) override { auto ptr = static_cast(bytes); diff --git a/tests/python/dragon/test_delete.py b/tests/python/dragon/test_delete.py index 94c2517..d284c05 100644 --- a/tests/python/dragon/test_delete.py +++ b/tests/python/dragon/test_delete.py @@ -20,6 +20,6 @@ def test_delete_raw_key(client, random_picklable): client.put_picklable(key, random_picklable) assert client.contains(key) - client.delete_key(key) + client.delete_item(IncomingHandle(key)) assert not client.contains(key) From 630802909e136e3c330e8c7e5310362e7d88ec42 Mon Sep 17 00:00:00 2001 From: Andrew Shao Date: Fri, 21 Aug 2026 07:04:01 -0700 Subject: [PATCH 3/4] Refactor for OutgoingHandle only --- include/radex/client_base.hpp | 2 +- src/cpp/client.cpp | 2 +- src/python/src/radex/clients/core.pyx | 2 +- tests/cpp/test_local_client.cpp | 8 ++++---- tests/python/dragon/test_delete.py | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/include/radex/client_base.hpp b/include/radex/client_base.hpp index 04bc1c5..b8fd1d8 100644 --- a/include/radex/client_base.hpp +++ b/include/radex/client_base.hpp @@ -217,7 +217,7 @@ class IClient { std::chrono::milliseconds timeout); /// Delete a typed value and its associated metadata. - void delete_item(const data::IncomingHandle &handle); + void delete_item(const data::OutgoingHandle &handle); public: /// Store a scalar value under `handle`. diff --git a/src/cpp/client.cpp b/src/cpp/client.cpp index f283b24..8010f2a 100644 --- a/src/cpp/client.cpp +++ b/src/cpp/client.cpp @@ -105,7 +105,7 @@ IClient::wait_for_item_info_ptr(const data::IncomingHandle &handle, get_item_info(fetch_bytes, handle)); } -void IClient::delete_item(const data::IncomingHandle &handle) { +void IClient::delete_item(const data::OutgoingHandle &handle) { delete_key(handle.key()); if (contains(handle.metadata_key())) { delete_key(handle.metadata_key()); diff --git a/src/python/src/radex/clients/core.pyx b/src/python/src/radex/clients/core.pyx index 424dd4d..5ef5689 100644 --- a/src/python/src/radex/clients/core.pyx +++ b/src/python/src/radex/clients/core.pyx @@ -65,7 +65,7 @@ cdef class PyClient: cdef np.number val = coerce_py_objects_to_np_numbers(value) return self._put_scalar(handle, val) - def delete_item(self, IncomingHandle handle): + def delete_item(self, OutgoingHandle handle): """Delete a typed value and its associated metadata.""" self._client.delete_item(handle.unwrap()[0]) diff --git a/tests/cpp/test_local_client.cpp b/tests/cpp/test_local_client.cpp index e23e39a..b585289 100644 --- a/tests/cpp/test_local_client.cpp +++ b/tests/cpp/test_local_client.cpp @@ -83,12 +83,12 @@ TEMPLATE_TEST_CASE("In memory client test cases", "[in-mem]", std::int32_t, SECTION("Client can delete a scalar value and its metadata") { const radex::data::OutgoingHandle outgoing{"my-scalar"}; - const radex::data::IncomingHandle incoming{"my-scalar"}; + const radex::data::OutgoingHandle deletion_handle{"my-scalar"}; client.put_scalar(outgoing, TestType{}); REQUIRE(client.contains(outgoing.key())); REQUIRE(client.contains(outgoing.metadata_key())); - client.delete_item(incoming); + client.delete_item(deletion_handle); REQUIRE_FALSE(client.contains(outgoing.key())); REQUIRE_FALSE(client.contains(outgoing.metadata_key())); @@ -96,14 +96,14 @@ TEMPLATE_TEST_CASE("In memory client test cases", "[in-mem]", std::int32_t, SECTION("Client can delete a tensor value and its metadata") { const radex::data::OutgoingHandle outgoing{"my-tensor-to-delete"}; - const radex::data::IncomingHandle incoming{"my-tensor-to-delete"}; + const radex::data::OutgoingHandle deletion_handle{"my-tensor-to-delete"}; const std::vector dims{2}; const std::vector data{TestType{1}, TestType{2}}; client.put_tensor(outgoing, dims, data); REQUIRE(client.contains(outgoing.key())); REQUIRE(client.contains(outgoing.metadata_key())); - client.delete_item(incoming); + client.delete_item(deletion_handle); REQUIRE_FALSE(client.contains(outgoing.key())); REQUIRE_FALSE(client.contains(outgoing.metadata_key())); diff --git a/tests/python/dragon/test_delete.py b/tests/python/dragon/test_delete.py index d284c05..f9dbdf0 100644 --- a/tests/python/dragon/test_delete.py +++ b/tests/python/dragon/test_delete.py @@ -8,7 +8,7 @@ def test_delete_typed_item(client, random_np_value): client.put_scalar(OutgoingHandle(key), random_np_value) assert client.contains(key) - client.delete_item(IncomingHandle(key)) + client.delete_item(OutgoingHandle(key)) assert not client.contains(key) with pytest.raises(Exception): @@ -20,6 +20,6 @@ def test_delete_raw_key(client, random_picklable): client.put_picklable(key, random_picklable) assert client.contains(key) - client.delete_item(IncomingHandle(key)) + client.delete_item(OutgoingHandle(key)) assert not client.contains(key) From f063f9af28fbbc390e443352d038d34f17c8cb55 Mon Sep 17 00:00:00 2001 From: Andrew Shao Date: Fri, 21 Aug 2026 16:20:30 -0700 Subject: [PATCH 4/4] Check for meta key directly --- src/python/src/radex/clients/core.pxd | 2 +- tests/python/dragon/test_delete.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/python/src/radex/clients/core.pxd b/src/python/src/radex/clients/core.pxd index 8b6e51a..bd83b0c 100644 --- a/src/python/src/radex/clients/core.pxd +++ b/src/python/src/radex/clients/core.pxd @@ -36,4 +36,4 @@ cdef extern from "radex/client.hpp" namespace "radex": unique_ptr[ItemInfo] get_item_info_ptr(const IncomingHandle&) except +raise_py_error unique_ptr[ItemInfo] wait_for_item_info_ptr( const IncomingHandle&, milliseconds) except +raise_py_error - void delete_item(const IncomingHandle&) except +raise_py_error + void delete_item(const OutgoingHandle&) except +raise_py_error diff --git a/tests/python/dragon/test_delete.py b/tests/python/dragon/test_delete.py index f9dbdf0..65b6a37 100644 --- a/tests/python/dragon/test_delete.py +++ b/tests/python/dragon/test_delete.py @@ -11,8 +11,7 @@ def test_delete_typed_item(client, random_np_value): client.delete_item(OutgoingHandle(key)) assert not client.contains(key) - with pytest.raises(Exception): - client.get_scalar(IncomingHandle(key)) + assert not client.contains(f"meta::{key}") def test_delete_raw_key(client, random_picklable):