From 2a4ccdd5372dedf8e719c3b719a9c942018c163a Mon Sep 17 00:00:00 2001 From: Yuce Tekol Date: Mon, 23 Feb 2026 11:16:25 +0300 Subject: [PATCH 1/3] Ported the List proxy to asyncio --- hazelcast/asyncio/__init__.py | 3 ++- hazelcast/internal/asyncio_client.py | 13 +++++++++++++ hazelcast/internal/asyncio_proxy/base.py | 13 +++++++++++++ hazelcast/internal/asyncio_proxy/manager.py | 3 +++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/hazelcast/asyncio/__init__.py b/hazelcast/asyncio/__init__.py index ac902ad2b1..0f506e64d9 100644 --- a/hazelcast/asyncio/__init__.py +++ b/hazelcast/asyncio/__init__.py @@ -3,8 +3,9 @@ warnings.warn("Asyncio API for Hazelcast Python Client is BETA. DO NOT use it in production.") del warnings -__all__ = ["EntryEventCallable", "HazelcastClient", "Map", "VectorCollection"] +__all__ = ["EntryEventCallable", "HazelcastClient", "List", "Map", "VectorCollection"] from hazelcast.internal.asyncio_client import HazelcastClient +from hazelcast.internal.asyncio_proxy.list import List from hazelcast.internal.asyncio_proxy.map import Map, EntryEventCallable from hazelcast.internal.asyncio_proxy.vector_collection import VectorCollection diff --git a/hazelcast/internal/asyncio_client.py b/hazelcast/internal/asyncio_client.py index 36ae965f4f..3758ef619b 100644 --- a/hazelcast/internal/asyncio_client.py +++ b/hazelcast/internal/asyncio_client.py @@ -23,11 +23,13 @@ dynamic_config_add_vector_collection_config_codec, ) from hazelcast.internal.asyncio_proxy.manager import ( + LIST_SERVICE, MAP_SERVICE, ProxyManager, VECTOR_SERVICE, ) from hazelcast.internal.asyncio_proxy.base import Proxy +from hazelcast.internal.asyncio_proxy.list import List from hazelcast.internal.asyncio_proxy.map import Map from hazelcast.internal.asyncio_reactor import AsyncioReactor from hazelcast.serialization import SerializationServiceV1 @@ -248,6 +250,17 @@ async def _start(self): raise _logger.info("Client started") + async def get_list(self, name: str) -> List[KeyType]: + """Returns the distributed list instance with the specified name. + + Args: + name: Name of the distributed list. + + Returns: + Distributed list instance with the specified name. + """ + return await self._proxy_manager.get_or_create(LIST_SERVICE, name) + async def get_map(self, name: str) -> Map[KeyType, ValueType]: """Returns the distributed map instance with the specified name. diff --git a/hazelcast/internal/asyncio_proxy/base.py b/hazelcast/internal/asyncio_proxy/base.py index 8edd64a53b..03c3656b5b 100644 --- a/hazelcast/internal/asyncio_proxy/base.py +++ b/hazelcast/internal/asyncio_proxy/base.py @@ -6,6 +6,7 @@ from hazelcast.core import MemberInfo from hazelcast.types import KeyType, ValueType, ItemType, MessageType, BlockingProxyType from hazelcast.internal.asyncio_invocation import Invocation +from hazelcast.internal.asyncio_partition import string_partition_strategy from hazelcast.util import get_attr_name MAX_SIZE = float("inf") @@ -91,6 +92,18 @@ async def _ainvoke_on_partition( return await fut +class PartitionSpecificProxy(Proxy, abc.ABC): + """Provides basic functionality for Partition Specific Proxies.""" + + def __init__(self, service_name, name, context): + super(PartitionSpecificProxy, self).__init__(service_name, name, context) + partition_key = context.serialization_service.to_data(string_partition_strategy(name)) + self._partition_id = context.partition_service.get_partition_id(partition_key) + + def _invoke(self, request, response_handler=_no_op_response_handler) -> asyncio.Future: + return self._invoke_on_partition(request, self._partition_id, response_handler) + + class ItemEventType: """Type of item events.""" diff --git a/hazelcast/internal/asyncio_proxy/manager.py b/hazelcast/internal/asyncio_proxy/manager.py index 9daeca0e1f..c2cfdfc26a 100644 --- a/hazelcast/internal/asyncio_proxy/manager.py +++ b/hazelcast/internal/asyncio_proxy/manager.py @@ -1,5 +1,6 @@ import typing +from hazelcast.internal.asyncio_proxy.list import create_list_proxy from hazelcast.internal.asyncio_proxy.vector_collection import ( VectorCollection, create_vector_collection_proxy, @@ -10,6 +11,7 @@ from hazelcast.internal.asyncio_proxy.map import create_map_proxy from hazelcast.util import to_list +LIST_SERVICE = "hz:impl:listService" MAP_SERVICE = "hz:impl:mapService" VECTOR_SERVICE = "hz:service:vector" @@ -17,6 +19,7 @@ str, typing.Callable[[str, str, typing.Any], typing.Coroutine[typing.Any, typing.Any, typing.Any]], ] = { + LIST_SERVICE: create_list_proxy, MAP_SERVICE: create_map_proxy, VECTOR_SERVICE: create_vector_collection_proxy, } From 869f5f6d2a36177ca2860908e51c6751c1aa5884 Mon Sep 17 00:00:00 2001 From: Yuce Tekol Date: Mon, 23 Feb 2026 11:17:16 +0300 Subject: [PATCH 2/3] Added the missing bits --- hazelcast/internal/asyncio_proxy/list.py | 512 +++++++++++++++++++ tests/integration/asyncio/proxy/list_test.py | 316 ++++++++++++ 2 files changed, 828 insertions(+) create mode 100644 hazelcast/internal/asyncio_proxy/list.py create mode 100644 tests/integration/asyncio/proxy/list_test.py diff --git a/hazelcast/internal/asyncio_proxy/list.py b/hazelcast/internal/asyncio_proxy/list.py new file mode 100644 index 0000000000..6dd09fbde5 --- /dev/null +++ b/hazelcast/internal/asyncio_proxy/list.py @@ -0,0 +1,512 @@ +import typing + +from hazelcast.protocol.codec import ( + list_add_all_codec, + list_add_all_with_index_codec, + list_add_codec, + list_add_listener_codec, + list_add_with_index_codec, + list_clear_codec, + list_compare_and_remove_all_codec, + list_compare_and_retain_all_codec, + list_contains_all_codec, + list_contains_codec, + list_get_all_codec, + list_get_codec, + list_index_of_codec, + list_is_empty_codec, + list_iterator_codec, + list_last_index_of_codec, + list_list_iterator_codec, + list_remove_codec, + list_remove_listener_codec, + list_remove_with_index_codec, + list_set_codec, + list_size_codec, + list_sub_codec, +) +from hazelcast.internal.asyncio_proxy.base import ( + PartitionSpecificProxy, + ItemEvent, + ItemEventType, +) +from hazelcast.types import ItemType +from hazelcast.serialization.compact import SchemaNotReplicatedError +from hazelcast.util import check_not_none, deserialize_list_in_place + + +class List(PartitionSpecificProxy, typing.Generic[ItemType]): + """Concurrent, distributed implementation of List. + + The Hazelcast List is not a partitioned data-structure. So all the content + of the List is stored in a single machine (and in the backup). So the List + will not scale by adding more members in the cluster. + + Example: + >>> my_list = await client.get_list("my_list") + >>> print("list.add", await my_list.add("item")) + >>> print("list.size", await my_list.size()) + + Warning: + Asyncio client list proxy is not thread-safe, do not access it from other threads. + """ + + async def add(self, item: ItemType) -> bool: + """Adds the specified item to the end of this list. + + Args: + item: the specified item to be appended to this list. + + Returns: + ``True`` if item is added, ``False`` otherwise. + """ + check_not_none(item, "Value can't be None") + try: + element_data = self._to_data(item) + except SchemaNotReplicatedError as e: + return await self._send_schema_and_retry(e, self.add, item) + + request = list_add_codec.encode_request(self.name, element_data) + return await self._invoke(request, list_add_codec.decode_response) + + async def add_at(self, index: int, item: ItemType) -> None: + """Adds the specified item at the specific position in this list. + Element in this position and following elements are shifted to the + right, if any. + + Args: + index: The specified index to insert the item. + item: The specified item to be inserted. + """ + check_not_none(item, "Value can't be None") + try: + element_data = self._to_data(item) + except SchemaNotReplicatedError as e: + return await self._send_schema_and_retry(e, self.add_at, index, item) + + request = list_add_with_index_codec.encode_request(self.name, index, element_data) + return await self._invoke(request) + + async def add_all(self, items: typing.Sequence[ItemType]) -> bool: + """Adds all of the items in the specified collection to the end of this + list. + + The order of new elements is determined by the specified collection's + iterator. + + Args: + items: The specified collection which includes the elements to be + added to list. + + Returns: + ``True`` if this call changed the list, ``False`` otherwise. + """ + check_not_none(items, "Value can't be None") + + try: + data_items = [] + for item in items: + check_not_none(item, "Value can't be None") + data_items.append(self._to_data(item)) + except SchemaNotReplicatedError as e: + return await self._send_schema_and_retry(e, self.add_all, items) + + request = list_add_all_codec.encode_request(self.name, data_items) + return await self._invoke(request, list_add_all_codec.decode_response) + + async def add_all_at(self, index: int, items: typing.Sequence[ItemType]) -> bool: + """Adds all of the elements in the specified collection into this list + at the specified position. + + Elements in this positions and following elements are shifted to the + right, if any. The order of new elements is determined by the specified + collection's iterator. + + Args: + index: The specified index at which the first element of specified + collection is added. + items: The specified collection which includes the elements to be + added to list. + + Returns: + ``True`` if this call changed the list, ``False`` otherwise. + """ + check_not_none(items, "Value can't be None") + try: + data_items = [] + for item in items: + check_not_none(item, "Value can't be None") + data_items.append(self._to_data(item)) + except SchemaNotReplicatedError as e: + return await self._send_schema_and_retry(e, self.add_all_at, index, items) + + request = list_add_all_with_index_codec.encode_request(self.name, index, data_items) + return await self._invoke(request, list_add_all_with_index_codec.decode_response) + + async def add_listener( + self, + include_value: bool = False, + item_added_func: typing.Callable[[ItemEvent[ItemType]], None] = None, + item_removed_func: typing.Callable[[ItemEvent[ItemType]], None] = None, + ) -> str: + """Adds an item listener for this list. Listener will be notified for + all list add/remove events. + + Args: + include_value: Whether received events include the updated item or + not. + item_added_func: To be called when an item is added to this list. + item_removed_func: To be called when an item is deleted from this + list. + + Returns: + A registration id which is used as a key to remove the listener. + """ + request = list_add_listener_codec.encode_request(self.name, include_value, self._is_smart) + + def handle_event_item(item_data, uuid, event_type): + item = self._to_object(item_data) if include_value else None + member = self._context.cluster_service.get_member(uuid) + + item_event = ItemEvent(self.name, item, event_type, member) + if event_type == ItemEventType.ADDED: + if item_added_func: + item_added_func(item_event) + else: + if item_removed_func: + item_removed_func(item_event) + + return await self._register_listener( + request, + lambda r: list_add_listener_codec.decode_response(r), + lambda reg_id: list_remove_listener_codec.encode_request(self.name, reg_id), + lambda m: list_add_listener_codec.handle(m, handle_event_item), + ) + + async def clear(self) -> None: + """Clears the list. + + List will be empty with this call. + """ + request = list_clear_codec.encode_request(self.name) + return await self._invoke(request) + + async def contains(self, item: ItemType) -> bool: + """Determines whether this list contains the specified item or not. + + Args: + item: The specified item. + + Returns: + ``True`` if the specified item exists in this list, ``False`` + otherwise. + """ + check_not_none(item, "Value can't be None") + try: + item_data = self._to_data(item) + except SchemaNotReplicatedError as e: + return await self._send_schema_and_retry(e, self.contains, item) + + request = list_contains_codec.encode_request(self.name, item_data) + return await self._invoke(request, list_contains_codec.decode_response) + + async def contains_all(self, items: typing.Sequence[ItemType]) -> bool: + """Determines whether this list contains all of the items in specified + collection or not. + + Args: + items: The specified collection which includes the items to be + searched. + + Returns: + ``True`` if all of the items in specified collection exist in this + list, ``False`` otherwise. + """ + check_not_none(items, "Items can't be None") + try: + data_items = [] + for item in items: + check_not_none(item, "item can't be None") + data_items.append(self._to_data(item)) + except SchemaNotReplicatedError as e: + return await self._send_schema_and_retry(e, self.contains_all, items) + + request = list_contains_all_codec.encode_request(self.name, data_items) + return await self._invoke(request, list_contains_all_codec.decode_response) + + async def get(self, index: int) -> ItemType: + """Returns the item which is in the specified position in this list. + + Args: + index: the specified index of the item to be returned. + + Returns: + The item in the specified position in this list. + """ + + def handler(message): + return self._to_object(list_get_codec.decode_response(message)) + + request = list_get_codec.encode_request(self.name, index) + return await self._invoke(request, handler) + + async def get_all(self) -> typing.List[ItemType]: + """Returns all the items in this list. + + Returns: + All the items in this list. + """ + + def handler(message): + data_list = list_get_all_codec.decode_response(message) + return deserialize_list_in_place(data_list, self._to_object) + + request = list_get_all_codec.encode_request(self.name) + return await self._invoke(request, handler) + + async def iterator(self) -> typing.List[ItemType]: + """Returns an iterator over the elements in this list in proper + sequence, same with ``get_all``. + + Returns: + All the items in this list. + """ + + def handler(message): + data_list = list_iterator_codec.decode_response(message) + return deserialize_list_in_place(data_list, self._to_object) + + request = list_iterator_codec.encode_request(self.name) + return await self._invoke(request, handler) + + async def index_of(self, item: ItemType) -> int: + """Returns the first index of specified item's occurrences in this + list. + + If specified item is not present in this list, returns -1. + + Args: + item: The specified item to be searched for. + + Returns: + The first index of specified item's occurrences, ``-1`` if item + is not present in this list. + """ + check_not_none(item, "Value can't be None") + try: + item_data = self._to_data(item) + except SchemaNotReplicatedError as e: + return await self._send_schema_and_retry(e, self.index_of, item) + + request = list_index_of_codec.encode_request(self.name, item_data) + return await self._invoke(request, list_index_of_codec.decode_response) + + async def is_empty(self) -> bool: + """Determines whether this list is empty or not. + + Returns: + ``True`` if the list contains no elements, ``False`` otherwise. + """ + request = list_is_empty_codec.encode_request(self.name) + return await self._invoke(request, list_is_empty_codec.decode_response) + + async def last_index_of(self, item: ItemType) -> int: + """Returns the last index of specified item's occurrences in this list. + + If specified item is not present in this list, returns -1. + + Args: + item: The specified item to be searched for. + + Returns: + The last index of specified item's occurrences, ``-1`` if item is + not present in this list. + """ + check_not_none(item, "Value can't be None") + try: + item_data = self._to_data(item) + except SchemaNotReplicatedError as e: + return await self._send_schema_and_retry(e, self.last_index_of, item) + + request = list_last_index_of_codec.encode_request(self.name, item_data) + return await self._invoke(request, list_last_index_of_codec.decode_response) + + async def list_iterator(self, index: int = 0) -> typing.List[ItemType]: + """Returns a list iterator of the elements in this list. + + If an index is provided, iterator starts from this index. + + Args: + index: Index of first element to be returned from the list + iterator. + + Returns: + List of the elements in this list. + """ + + def handler(message): + data_list = list_list_iterator_codec.decode_response(message) + return deserialize_list_in_place(data_list, self._to_object) + + request = list_list_iterator_codec.encode_request(self.name, index) + return await self._invoke(request, handler) + + async def remove(self, item: ItemType) -> bool: + """Removes the specified element's first occurrence from the list if it + exists in this list. + + Args: + item: The specified element. + + Returns: + ``True`` if the specified element is present in this list, + ``False`` otherwise. + """ + check_not_none(item, "Value can't be None") + try: + item_data = self._to_data(item) + except SchemaNotReplicatedError as e: + return await self._send_schema_and_retry(e, self.remove, item) + + request = list_remove_codec.encode_request(self.name, item_data) + return await self._invoke(request, list_remove_codec.decode_response) + + async def remove_at(self, index: int) -> ItemType: + """Removes the item at the specified position in this list. + + Element in this position and following elements are shifted to the + left, if any. + + Args: + index: Index of the item to be removed. + + Returns: + The item previously at the specified index. + """ + + def handler(message): + return self._to_object(list_remove_with_index_codec.decode_response(message)) + + request = list_remove_with_index_codec.encode_request(self.name, index) + return await self._invoke(request, handler) + + async def remove_all(self, items: typing.Sequence[ItemType]) -> bool: + """Removes all of the elements that is present in the specified + collection from this list. + + Args: + items: The specified collection. + + Returns: + ``True`` if this list changed as a result of the call, + ``False`` otherwise. + """ + check_not_none(items, "Value can't be None") + try: + data_items = [] + for item in items: + check_not_none(item, "Value can't be None") + data_items.append(self._to_data(item)) + except SchemaNotReplicatedError as e: + return await self._send_schema_and_retry(e, self.remove_all, items) + + request = list_compare_and_remove_all_codec.encode_request(self.name, data_items) + return await self._invoke(request, list_compare_and_remove_all_codec.decode_response) + + async def remove_listener(self, registration_id: str) -> bool: + """Removes the specified item listener. + + Returns silently if the specified listener was not added before. + + Args: + registration_id: Id of the listener to be deleted. + + Returns: + ``True`` if the item listener is removed, ``False`` otherwise. + """ + return await self._deregister_listener(registration_id) + + async def retain_all(self, items: typing.Sequence[ItemType]) -> bool: + """Retains only the items that are contained in the specified + collection. + + It means, items which are not present in the specified collection are + removed from this list. + + Args: + items: Collections which includes the elements to be retained in + this list. + + Returns: + ``True`` if this list changed as a result of the call, ``False`` + otherwise. + """ + check_not_none(items, "Value can't be None") + try: + data_items = [] + for item in items: + check_not_none(item, "Value can't be None") + data_items.append(self._to_data(item)) + except SchemaNotReplicatedError as e: + return await self._send_schema_and_retry(e, self.retain_all, items) + + request = list_compare_and_retain_all_codec.encode_request(self.name, data_items) + return await self._invoke(request, list_compare_and_retain_all_codec.decode_response) + + async def size(self) -> int: + """Returns the number of elements in this list. + + Returns: + Number of elements in this list. + """ + request = list_size_codec.encode_request(self.name) + return await self._invoke(request, list_size_codec.decode_response) + + async def set_at(self, index: int, item: ItemType) -> ItemType: + """Replaces the specified element with the element at the specified + position in this list. + + Args: + index: Index of the item to be replaced. + item: Item to be stored. + + Returns: + The previous item in the specified index. + """ + check_not_none(item, "Value can't be None") + try: + element_data = self._to_data(item) + except SchemaNotReplicatedError as e: + return await self._send_schema_and_retry(e, self.set_at, index, item) + + def handler(message): + return self._to_object(list_set_codec.decode_response(message)) + + request = list_set_codec.encode_request(self.name, index, element_data) + return await self._invoke(request, handler) + + async def sub_list(self, from_index: int, to_index: int) -> typing.List[ItemType]: + """Returns a sublist from this list, from from_index(inclusive) to + to_index(exclusive). + + The returned list is backed by this list, so non-structural changes in + the returned list are reflected in this list, and vice-versa. + + Args: + from_index: The start point(inclusive) of the sub_list. + to_index: The end point(exclusive) of the sub_list. + + Returns: + A view of the specified range within this list. + """ + + def handler(message): + data_list = list_sub_codec.decode_response(message) + return deserialize_list_in_place(data_list, self._to_object) + + request = list_sub_codec.encode_request(self.name, from_index, to_index) + return await self._invoke(request, handler) + + +async def create_list_proxy(service_name, name, context): + return List(service_name, name, context) diff --git a/tests/integration/asyncio/proxy/list_test.py b/tests/integration/asyncio/proxy/list_test.py new file mode 100644 index 0000000000..a82286e825 --- /dev/null +++ b/tests/integration/asyncio/proxy/list_test.py @@ -0,0 +1,316 @@ +import asyncio + +from hazelcast.internal.asyncio_proxy.base import ItemEventType +from tests.integration.asyncio.base import SingleMemberTestCase +from tests.util import random_string, event_collector + + +class ListTest(SingleMemberTestCase): + @classmethod + def configure_client(cls, config): + config["cluster_name"] = cls.cluster.id + return config + + async def asyncSetUp(self): + await super().asyncSetUp() + self.list = await self.client.get_list(random_string()) + + async def asyncTearDown(self): + await self.list.destroy() + await super().asyncTearDown() + + async def fill_list(self, items=None): + if items is None: + items = ["item-%d" % i for i in range(5)] + for item in items: + await self.list.add(item) + return items + + async def test_add(self): + result = await self.list.add("item") + self.assertTrue(result) + self.assertEqual(await self.list.get(0), "item") + + async def test_add_null_element(self): + with self.assertRaises(AssertionError): + await self.list.add(None) + + async def test_add_appends_to_end(self): + await self.list.add("first") + await self.list.add("second") + self.assertEqual(await self.list.get(1), "second") + + async def test_add_at(self): + await self.list.add("a") + await self.list.add("c") + await self.list.add_at(1, "b") + self.assertEqual(await self.list.get(1), "b") + self.assertEqual(await self.list.get(2), "c") + + async def test_add_at_null_element(self): + with self.assertRaises(AssertionError): + await self.list.add_at(0, None) + + async def test_add_all(self): + items = ["1", "2", "3"] + result = await self.list.add_all(items) + self.assertTrue(result) + self.assertEqual(await self.list.get(0), "1") + self.assertEqual(await self.list.get(1), "2") + self.assertEqual(await self.list.get(2), "3") + + async def test_add_all_null_element(self): + with self.assertRaises(AssertionError): + await self.list.add_all(["1", None, "3"]) + + async def test_add_all_null_items(self): + with self.assertRaises(AssertionError): + await self.list.add_all(None) + + async def test_add_all_at(self): + await self.list.add("0") + items = ["1", "2", "3"] + result = await self.list.add_all_at(1, items) + self.assertTrue(result) + tail = await self.list.list_iterator(1) + self.assertCountEqual(tail, items) + + async def test_add_all_at_null_element(self): + with self.assertRaises(AssertionError): + await self.list.add_all_at(0, ["1", None]) + + async def test_add_all_at_null_items(self): + with self.assertRaises(AssertionError): + await self.list.add_all_at(0, None) + + async def test_add_listener_item_added(self): + collector = event_collector() + await self.list.add_listener(include_value=False, item_added_func=collector) + await self.list.add("item-value") + + def assert_event(): + self.assertEqual(len(collector.events), 1) + event = collector.events[0] + self.assertIsNone(event.item) + self.assertEqual(event.event_type, ItemEventType.ADDED) + + await self.assertTrueEventually(assert_event, 5) + + async def test_add_listener_item_added_include_value(self): + collector = event_collector() + await self.list.add_listener(include_value=True, item_added_func=collector) + await self.list.add("item-value") + + def assert_event(): + self.assertEqual(len(collector.events), 1) + event = collector.events[0] + self.assertEqual(event.item, "item-value") + self.assertEqual(event.event_type, ItemEventType.ADDED) + + await self.assertTrueEventually(assert_event, 5) + + async def test_add_listener_item_removed(self): + collector = event_collector() + await self.list.add_listener(include_value=False, item_removed_func=collector) + await self.list.add("item-value") + await self.list.remove("item-value") + + def assert_event(): + self.assertEqual(len(collector.events), 1) + event = collector.events[0] + self.assertIsNone(event.item) + self.assertEqual(event.event_type, ItemEventType.REMOVED) + + await self.assertTrueEventually(assert_event, 5) + + async def test_add_listener_item_removed_include_value(self): + collector = event_collector() + await self.list.add_listener(include_value=True, item_removed_func=collector) + await self.list.add("item-value") + await self.list.remove("item-value") + + def assert_event(): + self.assertEqual(len(collector.events), 1) + event = collector.events[0] + self.assertEqual(event.item, "item-value") + self.assertEqual(event.event_type, ItemEventType.REMOVED) + + await self.assertTrueEventually(assert_event, 5) + + async def test_remove_listener(self): + collector = event_collector() + reg_id = await self.list.add_listener(include_value=False, item_added_func=collector) + await self.list.remove_listener(reg_id) + await self.list.add("item-value") + + await asyncio.sleep(1) + self.assertEqual(len(collector.events), 0) + + async def test_clear(self): + await self.fill_list() + await self.list.clear() + self.assertEqual(await self.list.size(), 0) + + async def test_contains_when_present(self): + await self.fill_list(["a", "b", "c"]) + self.assertTrue(await self.list.contains("b")) + + async def test_contains_when_missing(self): + await self.fill_list(["a", "b", "c"]) + self.assertFalse(await self.list.contains("z")) + + async def test_contains_null_element(self): + with self.assertRaises(AssertionError): + await self.list.contains(None) + + async def test_contains_all_when_present(self): + items = ["1", "2", "3"] + await self.fill_list(items) + self.assertTrue(await self.list.contains_all(items)) + + async def test_contains_all_when_partial(self): + await self.fill_list(["1", "2", "3"]) + self.assertFalse(await self.list.contains_all(["1", "2", "99"])) + + async def test_contains_all_null_items(self): + with self.assertRaises(AssertionError): + await self.list.contains_all(None) + + async def test_get(self): + await self.list.add("hello") + self.assertEqual(await self.list.get(0), "hello") + + async def test_get_all(self): + items = ["1", "2", "3"] + await self.fill_list(items) + self.assertEqual(await self.list.get_all(), items) + + async def test_get_all_empty(self): + self.assertEqual(await self.list.get_all(), []) + + async def test_iterator(self): + items = ["1", "2", "3"] + await self.fill_list(items) + result = await self.list.iterator() + self.assertEqual(result, items) + + async def test_index_of(self): + await self.fill_list(["a", "b", "c"]) + self.assertEqual(await self.list.index_of("b"), 1) + + async def test_index_of_not_found(self): + await self.fill_list(["a", "b", "c"]) + self.assertEqual(await self.list.index_of("z"), -1) + + async def test_index_of_null_element(self): + with self.assertRaises(AssertionError): + await self.list.index_of(None) + + async def test_is_empty_when_empty(self): + self.assertTrue(await self.list.is_empty()) + + async def test_is_empty_when_not_empty(self): + await self.list.add("x") + self.assertFalse(await self.list.is_empty()) + + async def test_last_index_of(self): + await self.fill_list(["1", "2", "2", "3"]) + self.assertEqual(await self.list.last_index_of("2"), 2) + + async def test_last_index_of_not_found(self): + await self.fill_list(["1", "2", "3"]) + self.assertEqual(await self.list.last_index_of("z"), -1) + + async def test_last_index_of_null_element(self): + with self.assertRaises(AssertionError): + await self.list.last_index_of(None) + + async def test_list_iterator(self): + items = ["1", "2", "3"] + await self.fill_list(items) + result = await self.list.list_iterator() + self.assertEqual(result, items) + + async def test_list_iterator_with_index(self): + await self.fill_list(["1", "2", "3"]) + result = await self.list.list_iterator(1) + self.assertEqual(result, ["2", "3"]) + + async def test_remove_existing(self): + await self.list.add("item") + result = await self.list.remove("item") + self.assertTrue(result) + self.assertEqual(await self.list.size(), 0) + + async def test_remove_non_existing(self): + result = await self.list.remove("no-such-item") + self.assertFalse(result) + + async def test_remove_null_element(self): + with self.assertRaises(AssertionError): + await self.list.remove(None) + + async def test_remove_at(self): + await self.list.add("item") + removed = await self.list.remove_at(0) + self.assertEqual(removed, "item") + self.assertEqual(await self.list.size(), 0) + + async def test_remove_at_returns_previous_element(self): + await self.fill_list(["a", "b", "c"]) + removed = await self.list.remove_at(1) + self.assertEqual(removed, "b") + self.assertEqual(await self.list.size(), 2) + + async def test_remove_all(self): + await self.fill_list(["1", "2", "3"]) + result = await self.list.remove_all(["2", "3"]) + self.assertTrue(result) + self.assertEqual(await self.list.get_all(), ["1"]) + + async def test_remove_all_null_items(self): + with self.assertRaises(AssertionError): + await self.list.remove_all(None) + + async def test_retain_all(self): + await self.fill_list(["1", "2", "3"]) + result = await self.list.retain_all(["2", "3"]) + self.assertTrue(result) + self.assertEqual(await self.list.get_all(), ["2", "3"]) + + async def test_retain_all_null_items(self): + with self.assertRaises(AssertionError): + await self.list.retain_all(None) + + async def test_size(self): + items = ["1", "2", "3"] + await self.fill_list(items) + self.assertEqual(await self.list.size(), len(items)) + + async def test_size_empty(self): + self.assertEqual(await self.list.size(), 0) + + async def test_set_at(self): + await self.fill_list(["1", "2", "3"]) + previous = await self.list.set_at(1, "22") + self.assertEqual(previous, "2") + self.assertEqual(await self.list.get(1), "22") + + async def test_set_at_null_element(self): + await self.list.add("item") + with self.assertRaises(AssertionError): + await self.list.set_at(0, None) + + async def test_sub_list(self): + await self.fill_list(["1", "2", "3", "4"]) + result = await self.list.sub_list(1, 3) + self.assertEqual(result, ["2", "3"]) + + async def test_sub_list_full_range(self): + items = ["a", "b", "c"] + await self.fill_list(items) + result = await self.list.sub_list(0, 3) + self.assertEqual(result, items) + + def test_str(self): + self.assertTrue(str(self.list).startswith("List")) From cc8036620fe6368ab7ef6edfdddeea33f5847555 Mon Sep 17 00:00:00 2001 From: Yuce Tekol Date: Mon, 23 Feb 2026 11:25:11 +0300 Subject: [PATCH 3/3] Port same List tests to asyncio --- tests/integration/asyncio/proxy/list_test.py | 362 ++++++++----------- 1 file changed, 154 insertions(+), 208 deletions(-) diff --git a/tests/integration/asyncio/proxy/list_test.py b/tests/integration/asyncio/proxy/list_test.py index a82286e825..6fa7465225 100644 --- a/tests/integration/asyncio/proxy/list_test.py +++ b/tests/integration/asyncio/proxy/list_test.py @@ -1,5 +1,3 @@ -import asyncio - from hazelcast.internal.asyncio_proxy.base import ItemEventType from tests.integration.asyncio.base import SingleMemberTestCase from tests.util import random_string, event_collector @@ -19,71 +17,7 @@ async def asyncTearDown(self): await self.list.destroy() await super().asyncTearDown() - async def fill_list(self, items=None): - if items is None: - items = ["item-%d" % i for i in range(5)] - for item in items: - await self.list.add(item) - return items - - async def test_add(self): - result = await self.list.add("item") - self.assertTrue(result) - self.assertEqual(await self.list.get(0), "item") - - async def test_add_null_element(self): - with self.assertRaises(AssertionError): - await self.list.add(None) - - async def test_add_appends_to_end(self): - await self.list.add("first") - await self.list.add("second") - self.assertEqual(await self.list.get(1), "second") - - async def test_add_at(self): - await self.list.add("a") - await self.list.add("c") - await self.list.add_at(1, "b") - self.assertEqual(await self.list.get(1), "b") - self.assertEqual(await self.list.get(2), "c") - - async def test_add_at_null_element(self): - with self.assertRaises(AssertionError): - await self.list.add_at(0, None) - - async def test_add_all(self): - items = ["1", "2", "3"] - result = await self.list.add_all(items) - self.assertTrue(result) - self.assertEqual(await self.list.get(0), "1") - self.assertEqual(await self.list.get(1), "2") - self.assertEqual(await self.list.get(2), "3") - - async def test_add_all_null_element(self): - with self.assertRaises(AssertionError): - await self.list.add_all(["1", None, "3"]) - - async def test_add_all_null_items(self): - with self.assertRaises(AssertionError): - await self.list.add_all(None) - - async def test_add_all_at(self): - await self.list.add("0") - items = ["1", "2", "3"] - result = await self.list.add_all_at(1, items) - self.assertTrue(result) - tail = await self.list.list_iterator(1) - self.assertCountEqual(tail, items) - - async def test_add_all_at_null_element(self): - with self.assertRaises(AssertionError): - await self.list.add_all_at(0, ["1", None]) - - async def test_add_all_at_null_items(self): - with self.assertRaises(AssertionError): - await self.list.add_all_at(0, None) - - async def test_add_listener_item_added(self): + async def test_add_entry_listener_item_added(self): collector = event_collector() await self.list.add_listener(include_value=False, item_added_func=collector) await self.list.add("item-value") @@ -91,12 +25,12 @@ async def test_add_listener_item_added(self): def assert_event(): self.assertEqual(len(collector.events), 1) event = collector.events[0] - self.assertIsNone(event.item) + self.assertEqual(event.item, None) self.assertEqual(event.event_type, ItemEventType.ADDED) await self.assertTrueEventually(assert_event, 5) - async def test_add_listener_item_added_include_value(self): + async def test_add_entry_listener_item_added_include_value(self): collector = event_collector() await self.list.add_listener(include_value=True, item_added_func=collector) await self.list.add("item-value") @@ -109,7 +43,7 @@ def assert_event(): await self.assertTrueEventually(assert_event, 5) - async def test_add_listener_item_removed(self): + async def test_add_entry_listener_item_removed(self): collector = event_collector() await self.list.add_listener(include_value=False, item_removed_func=collector) await self.list.add("item-value") @@ -118,12 +52,12 @@ async def test_add_listener_item_removed(self): def assert_event(): self.assertEqual(len(collector.events), 1) event = collector.events[0] - self.assertIsNone(event.item) + self.assertEqual(event.item, None) self.assertEqual(event.event_type, ItemEventType.REMOVED) await self.assertTrueEventually(assert_event, 5) - async def test_add_listener_item_removed_include_value(self): + async def test_add_entry_listener_item_removed_include_value(self): collector = event_collector() await self.list.add_listener(include_value=True, item_removed_func=collector) await self.list.add("item-value") @@ -137,180 +71,192 @@ def assert_event(): await self.assertTrueEventually(assert_event, 5) - async def test_remove_listener(self): + async def test_remove_entry_listener_item_added(self): collector = event_collector() reg_id = await self.list.add_listener(include_value=False, item_added_func=collector) await self.list.remove_listener(reg_id) await self.list.add("item-value") - await asyncio.sleep(1) - self.assertEqual(len(collector.events), 0) - - async def test_clear(self): - await self.fill_list() - await self.list.clear() - self.assertEqual(await self.list.size(), 0) + def assert_event(): + self.assertEqual(len(collector.events), 0) + if len(collector.events) > 0: + event = collector.events[0] + self.assertEqual(event.item, None) + self.assertEqual(event.event_type, ItemEventType.ADDED) - async def test_contains_when_present(self): - await self.fill_list(["a", "b", "c"]) - self.assertTrue(await self.list.contains("b")) + await self.assertTrueEventually(assert_event, 5) - async def test_contains_when_missing(self): - await self.fill_list(["a", "b", "c"]) - self.assertFalse(await self.list.contains("z")) + async def test_add(self): + add_resp = await self.list.add("Test") + result = await self.list.get(0) + self.assertTrue(add_resp) + self.assertEqual(result, "Test") - async def test_contains_null_element(self): + async def test_add_null_element(self): with self.assertRaises(AssertionError): - await self.list.contains(None) - - async def test_contains_all_when_present(self): - items = ["1", "2", "3"] - await self.fill_list(items) - self.assertTrue(await self.list.contains_all(items)) + await self.list.add(None) - async def test_contains_all_when_partial(self): - await self.fill_list(["1", "2", "3"]) - self.assertFalse(await self.list.contains_all(["1", "2", "99"])) + async def test_add_at(self): + await self.list.add_at(0, "Test0") + await self.list.add_at(1, "Test1") + result = await self.list.get(1) + self.assertEqual(result, "Test1") - async def test_contains_all_null_items(self): + async def test_add_at_null_element(self): with self.assertRaises(AssertionError): - await self.list.contains_all(None) - - async def test_get(self): - await self.list.add("hello") - self.assertEqual(await self.list.get(0), "hello") - - async def test_get_all(self): - items = ["1", "2", "3"] - await self.fill_list(items) - self.assertEqual(await self.list.get_all(), items) - - async def test_get_all_empty(self): - self.assertEqual(await self.list.get_all(), []) - - async def test_iterator(self): - items = ["1", "2", "3"] - await self.fill_list(items) - result = await self.list.iterator() - self.assertEqual(result, items) + await self.list.add_at(0, None) - async def test_index_of(self): - await self.fill_list(["a", "b", "c"]) - self.assertEqual(await self.list.index_of("b"), 1) + async def test_add_all(self): + _all = ["1", "2", "3"] + add_resp = await self.list.add_all(_all) + result0 = await self.list.get(0) + result1 = await self.list.get(1) + result2 = await self.list.get(2) + self.assertTrue(add_resp) + self.assertEqual(result0, "1") + self.assertEqual(result1, "2") + self.assertEqual(result2, "3") - async def test_index_of_not_found(self): - await self.fill_list(["a", "b", "c"]) - self.assertEqual(await self.list.index_of("z"), -1) + async def test_add_all_null_element(self): + _all = ["1", "2", "3", None] + with self.assertRaises(AssertionError): + await self.list.add_all(_all) - async def test_index_of_null_element(self): + async def test_add_all_null_elements(self): with self.assertRaises(AssertionError): - await self.list.index_of(None) + await self.list.add_all(None) - async def test_is_empty_when_empty(self): - self.assertTrue(await self.list.is_empty()) + async def test_add_all_at(self): + await self.list.add_at(0, "0") + _all = ["1", "2", "3"] + add_resp = await self.list.add_all_at(1, _all) + _all_resp = await self.list.list_iterator(1) + self.assertTrue(add_resp) + self.assertCountEqual(_all, _all_resp) - async def test_is_empty_when_not_empty(self): - await self.list.add("x") - self.assertFalse(await self.list.is_empty()) + async def test_add_all_at_null_element(self): + _all = ["1", "2", "3", None] + with self.assertRaises(AssertionError): + await self.list.add_all_at(0, _all) - async def test_last_index_of(self): - await self.fill_list(["1", "2", "2", "3"]) - self.assertEqual(await self.list.last_index_of("2"), 2) + async def test_add_all_at_null_elements(self): + with self.assertRaises(AssertionError): + await self.list.add_all_at(0, None) - async def test_last_index_of_not_found(self): - await self.fill_list(["1", "2", "3"]) - self.assertEqual(await self.list.last_index_of("z"), -1) + async def test_clear(self): + _all = ["1", "2", "3"] + await self.list.add_all(_all) + size = await self.list.size() + await self.list.clear() + size_cleared = await self.list.size() + self.assertEqual(size, len(_all)) + self.assertEqual(size_cleared, 0) + + async def test_contains(self): + _all = ["1", "2", "3"] + await self.list.add_all(_all) + contains_result = await self.list.contains("2") + self.assertTrue(contains_result) + + async def test_contains_all(self): + _all = ["1", "2", "3"] + await self.list.add_all(_all) + contains_result = await self.list.contains_all(_all) + self.assertTrue(contains_result) - async def test_last_index_of_null_element(self): - with self.assertRaises(AssertionError): - await self.list.last_index_of(None) + async def test_get_all(self): + _all = ["1", "2", "3"] + await self.list.add_all(_all) + all_result = await self.list.get_all() + self.assertEqual(all_result, _all) async def test_list_iterator(self): - items = ["1", "2", "3"] - await self.fill_list(items) - result = await self.list.list_iterator() - self.assertEqual(result, items) - - async def test_list_iterator_with_index(self): - await self.fill_list(["1", "2", "3"]) - result = await self.list.list_iterator(1) - self.assertEqual(result, ["2", "3"]) + _all = ["1", "2", "3"] + await self.list.add_all(_all) + list_iter = await self.list.list_iterator(1) + iter_result = [] + for item in list_iter: + iter_result.append(item) + self.assertEqual(iter_result, ["2", "3"]) + + async def test_list_iterator2(self): + _all = ["1", "2", "3"] + await self.list.add_all(_all) + list_iter = await self.list.list_iterator(1) + iter_val = list_iter[1] + self.assertEqual(iter_val, "3") + + async def test_iterator(self): + _all = ["1", "2", "3"] + await self.list.add_all(_all) + list_iter = await self.list.iterator() + iter_result = [] + for item in list_iter: + iter_result.append(item) + self.assertEqual(iter_result, _all) - async def test_remove_existing(self): - await self.list.add("item") - result = await self.list.remove("item") - self.assertTrue(result) - self.assertEqual(await self.list.size(), 0) + async def test_index_of(self): + _all = ["1", "2", "3"] + await self.list.add_all(_all) + idx = await self.list.index_of("2") + self.assertEqual(idx, 1) - async def test_remove_non_existing(self): - result = await self.list.remove("no-such-item") - self.assertFalse(result) + async def test_is_empty(self): + is_empty = await self.list.is_empty() + self.assertTrue(is_empty) - async def test_remove_null_element(self): - with self.assertRaises(AssertionError): - await self.list.remove(None) + async def test_last_index_of(self): + _all = ["1", "2", "2", "3"] + await self.list.add_all(_all) + idx = await self.list.last_index_of("2") + self.assertEqual(idx, 2) + + async def test_remove(self): + await self.list.add("Test") + remove_result = await self.list.remove("Test") + size = await self.list.size() + self.assertTrue(remove_result) + self.assertEqual(size, 0) async def test_remove_at(self): - await self.list.add("item") - removed = await self.list.remove_at(0) - self.assertEqual(removed, "item") - self.assertEqual(await self.list.size(), 0) - - async def test_remove_at_returns_previous_element(self): - await self.fill_list(["a", "b", "c"]) - removed = await self.list.remove_at(1) - self.assertEqual(removed, "b") - self.assertEqual(await self.list.size(), 2) + await self.list.add("Test") + remove_result = await self.list.remove_at(0) + size = await self.list.size() + self.assertTrue(remove_result) + self.assertEqual(size, 0) async def test_remove_all(self): - await self.fill_list(["1", "2", "3"]) - result = await self.list.remove_all(["2", "3"]) - self.assertTrue(result) - self.assertEqual(await self.list.get_all(), ["1"]) - - async def test_remove_all_null_items(self): - with self.assertRaises(AssertionError): - await self.list.remove_all(None) + _all = ["1", "2", "3"] + await self.list.add_all(_all) + await self.list.remove_all(["2", "3"]) + result = await self.list.get_all() + self.assertEqual(result, ["1"]) async def test_retain_all(self): - await self.fill_list(["1", "2", "3"]) - result = await self.list.retain_all(["2", "3"]) - self.assertTrue(result) - self.assertEqual(await self.list.get_all(), ["2", "3"]) - - async def test_retain_all_null_items(self): - with self.assertRaises(AssertionError): - await self.list.retain_all(None) + _all = ["1", "2", "3"] + await self.list.add_all(_all) + await self.list.retain_all(["2", "3"]) + result = await self.list.get_all() + self.assertEqual(result, ["2", "3"]) async def test_size(self): - items = ["1", "2", "3"] - await self.fill_list(items) - self.assertEqual(await self.list.size(), len(items)) - - async def test_size_empty(self): - self.assertEqual(await self.list.size(), 0) + _all = ["1", "2", "3"] + await self.list.add_all(_all) + size = await self.list.size() + self.assertEqual(size, len(_all)) async def test_set_at(self): - await self.fill_list(["1", "2", "3"]) - previous = await self.list.set_at(1, "22") - self.assertEqual(previous, "2") - self.assertEqual(await self.list.get(1), "22") - - async def test_set_at_null_element(self): - await self.list.add("item") - with self.assertRaises(AssertionError): - await self.list.set_at(0, None) + _all = ["1", "2", "3"] + await self.list.add_all(_all) + await self.list.set_at(1, "22") + result = await self.list.get(1) + self.assertEqual(result, "22") async def test_sub_list(self): - await self.fill_list(["1", "2", "3", "4"]) - result = await self.list.sub_list(1, 3) - self.assertEqual(result, ["2", "3"]) - - async def test_sub_list_full_range(self): - items = ["a", "b", "c"] - await self.fill_list(items) - result = await self.list.sub_list(0, 3) - self.assertEqual(result, items) + _all = ["1", "2", "3"] + await self.list.add_all(_all) + sub_list = await self.list.sub_list(1, 3) + self.assertEqual(sub_list, ["2", "3"]) def test_str(self): self.assertTrue(str(self.list).startswith("List"))