Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-tooz for openSUSE:Factory checked in at 2026-09-08 16:55:36 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-tooz (Old) and /work/SRC/openSUSE:Factory/.python-tooz.new.1265 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-tooz" Tue Sep 8 16:55:36 2026 rev:21 rq:1376150 version:9.1.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-tooz/python-tooz.changes 2026-08-09 21:37:16.350191529 +0200 +++ /work/SRC/openSUSE:Factory/.python-tooz.new.1265/python-tooz.changes 2026-09-08 16:57:52.238372613 +0200 @@ -1,0 +2,6 @@ +Mon Sep 7 09:50:00 UTC 2026 - Dirk Müller <[email protected]> + +- update to 9.1.0: + * Refactor retry wrapper + +------------------------------------------------------------------- Old: ---- tooz-9.0.1.tar.gz New: ---- tooz-9.1.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-tooz.spec ++++++ --- /var/tmp/diff_new_pack.NMrqdw/_old 2026-09-08 16:57:54.154452466 +0200 +++ /var/tmp/diff_new_pack.NMrqdw/_new 2026-09-08 16:57:54.158452633 +0200 @@ -17,7 +17,7 @@ Name: python-tooz -Version: 9.0.1 +Version: 9.1.0 Release: 0 Summary: Coordination library for distributed systems License: Apache-2.0 ++++++ tooz-9.0.1.tar.gz -> tooz-9.1.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tooz-9.0.1/AUTHORS new/tooz-9.1.0/AUTHORS --- old/tooz-9.0.1/AUTHORS 2026-07-30 19:09:21.000000000 +0200 +++ new/tooz-9.1.0/AUTHORS 2026-08-25 12:12:41.733296200 +0200 @@ -1,5 +1,6 @@ Abhijeet Malawade <[email protected]> Adam Gandelman <[email protected]> +Adam Harwell <[email protected]> Ade Lee <[email protected]> Akihiro Motoki <[email protected]> Alan Bishop <[email protected]> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tooz-9.0.1/ChangeLog new/tooz-9.1.0/ChangeLog --- old/tooz-9.0.1/ChangeLog 2026-07-30 19:09:21.000000000 +0200 +++ new/tooz-9.1.0/ChangeLog 2026-08-25 12:12:41.677296200 +0200 @@ -1,11 +1,18 @@ CHANGES ======= +9.1.0 +----- + +* Refactor retry wrapper + 9.0.1 ----- * retry: Don't treat bool as a numeric stop\_max\_delay +* Add get\_members\_with\_capabilities() * zookeeper: Fix wrong member used by get\_member\_capabilities +* Remove duplicated optional dependencies from test requirements 9.0.0 ----- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tooz-9.0.1/PKG-INFO new/tooz-9.1.0/PKG-INFO --- old/tooz-9.0.1/PKG-INFO 2026-07-30 19:09:21.935042100 +0200 +++ new/tooz-9.1.0/PKG-INFO 2026-08-25 12:12:41.826296600 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: tooz -Version: 9.0.1 +Version: 9.1.0 Summary: Coordination library for distributed systems. Author-email: OpenStack <[email protected]> License: Apache-2.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tooz-9.0.1/doc/requirements.txt new/tooz-9.1.0/doc/requirements.txt --- old/tooz-9.0.1/doc/requirements.txt 2026-07-30 19:08:36.000000000 +0200 +++ new/tooz-9.1.0/doc/requirements.txt 2026-08-25 12:11:46.708394500 +0200 @@ -1,22 +1,3 @@ sphinx>=2.0.0 # BSD openstackdocstheme>=2.2.1 # Apache-2.0 reno>=3.1.0 # Apache-2.0 - -# Install dependencies for tooz so that autodoc works. -## etcd3gw -etcd3gw>=0.1.0 # Apache-2.0 -## redis -redis>=3.1.0 # MIT -## postgresql -psycopg2>=2.5 # LGPL/ZPL -## mysql -PyMySQL>=0.6.2 # MIT License -## zookeeper -kazoo>=2.2 # Apache-2.0 -## memcached -pymemcache>=1.2.9 # Apache 2.0 License -## ipc -sysv-ipc>=0.6.8 # BSD License -## kubernetes -sherlock>=0.4.1 # MIT License -kubernetes>=2.8.1 # Apache-2.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tooz-9.0.1/test-requirements.txt new/tooz-9.1.0/test-requirements.txt --- old/tooz-9.0.1/test-requirements.txt 2026-07-30 19:08:36.000000000 +0200 +++ new/tooz-9.1.0/test-requirements.txt 2026-08-25 12:11:46.720394400 +0200 @@ -5,12 +5,3 @@ stestr>=2.0.0 ddt>=1.2.1 # MIT requests>=2.10.0 # Apache-2.0 -etcd3gw>=2.3.0 # Apache-2.0 -redis>=4.0.0 # MIT -psycopg2>=2.5 # LGPL/ZPL -PyMySQL>=0.6.2 # MIT License -kazoo>=2.6 # Apache-2.0 -pymemcache>=1.2.9 # Apache 2.0 License -sysv-ipc>=0.6.8 # BSD License -kubernetes>=2.8.1 # Apache-2.0 -sherlock>=0.4.1 # MIT License diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tooz-9.0.1/tooz/_retry.py new/tooz-9.1.0/tooz/_retry.py --- old/tooz-9.0.1/tooz/_retry.py 2026-07-30 19:08:36.000000000 +0200 +++ new/tooz-9.1.0/tooz/_retry.py 2026-08-25 12:11:46.721394500 +0200 @@ -14,7 +14,7 @@ # under the License. from collections.abc import Callable -import datetime +from datetime import timedelta from typing import Any, TypeVar import tenacity @@ -29,20 +29,18 @@ # TODO(stephenfin): Remove kwargs def retry( - stop_max_delay: int | float | bool | None = None, **kwargs: Any + stop_max_delay: int | float | timedelta | bool | None = None, **kwargs: Any ) -> Callable[[WrappedFn], WrappedFn]: - if isinstance( - stop_max_delay, (int, float, datetime.timedelta) - ) and not isinstance(stop_max_delay, bool): + if stop_max_delay is None or isinstance(stop_max_delay, bool): return tenacity.retry( wait=_default_wait, retry=tenacity.retry_never, - stop=stop.stop_after_delay(stop_max_delay), ) return tenacity.retry( wait=_default_wait, retry=tenacity.retry_never, + stop=stop.stop_after_delay(stop_max_delay), ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tooz-9.0.1/tooz/coordination.py new/tooz-9.1.0/tooz/coordination.py --- old/tooz-9.0.1/tooz/coordination.py 2026-07-30 19:08:36.000000000 +0200 +++ new/tooz-9.1.0/tooz/coordination.py 2026-08-25 12:11:46.721394500 +0200 @@ -630,6 +630,23 @@ """ raise tooz.NotImplemented("not implemented") + def get_members_with_capabilities( + self, group_id: bytes + ) -> CoordAsyncResult[dict[bytes, Capabilities | None]]: + """Return all members and their capabilities in one call. + + This is semantically equivalent to calling :meth:`get_members` + followed by :meth:`get_member_capabilities` for each member, + but drivers may override this method to fetch both in a single + backend round-trip (e.g. a prefix range query in etcd, or a + single HGETALL in Redis). + + :param group_id: the id of the group + :returns: a mapping of member_id to capabilities (or None) + :raises GroupNotCreated: if the group does not exist + """ + raise tooz.NotImplemented("not implemented") + def get_member_info( self, group_id: bytes, member_id: bytes ) -> CoordAsyncResult[dict[str, Any]]: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tooz-9.0.1/tooz/drivers/etcd3gw.py new/tooz-9.1.0/tooz/drivers/etcd3gw.py --- old/tooz-9.0.1/tooz/drivers/etcd3gw.py 2026-07-30 19:08:36.000000000 +0200 +++ new/tooz-9.1.0/tooz/drivers/etcd3gw.py 2026-08-25 12:11:46.722394500 +0200 @@ -481,6 +481,40 @@ self._executor.submit(_get_member_capabilities) ) + def get_members_with_capabilities( + self, group_id: bytes + ) -> coordination.CoordinatorResult[ + dict[bytes, coordination.Capabilities | None] + ]: + @_translate_failures + def _get_members_with_capabilities() -> dict[ + bytes, coordination.Capabilities | None + ]: + prefix_group = self._prefix_group(group_id) + result: dict[bytes, coordination.Capabilities | None] = {} + group_found = False + + for value, metadata in self.client.get_prefix( + prefix_group.decode() + ): + if metadata['key'] == prefix_group: + group_found = True + else: + member_id = metadata['key'][len(prefix_group) :] + caps = utils.loads(value) if value else None + result[member_id] = cast( + coordination.Capabilities | None, caps + ) + + if not group_found: + raise coordination.GroupNotCreated(group_id) + + return result + + return coordination.CoordinatorResult( + self._executor.submit(_get_members_with_capabilities) + ) + def update_capabilities( self, group_id: bytes, capabilities: coordination.Capabilities ) -> coordination.CoordinatorResult[None]: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tooz-9.0.1/tooz/drivers/file.py new/tooz-9.1.0/tooz/drivers/file.py --- old/tooz-9.0.1/tooz/drivers/file.py 2026-07-30 19:08:36.000000000 +0200 +++ new/tooz-9.1.0/tooz/drivers/file.py 2026-08-25 12:11:46.722394500 +0200 @@ -493,6 +493,57 @@ fut = self._executor.submit(_do_get_member_capabilities) return FileFutureResult(fut) + def get_members_with_capabilities( # type: ignore[override] + self, group_id: bytes + ) -> coordination.CoordAsyncResult[ + dict[bytes | str, coordination.Capabilities | None] + ]: + safe_group_id = self._make_filesystem_safe(group_id) + group_dir = os.path.join(self._group_dir, safe_group_id) + + @_lock_me(self._driver_lock) + def _do_get_members_with_capabilities() -> dict[ + bytes | str, coordination.Capabilities | None + ]: + if not os.path.isdir(group_dir): + raise coordination.GroupNotCreated(group_id) + result: dict[bytes | str, coordination.Capabilities | None] = {} + try: + entries = os.listdir(group_dir) + except OSError as e: + if e.errno != errno.ENOENT: + raise + return result + for entry in entries: + if not entry.endswith('.raw'): + continue + entry_path = os.path.join(group_dir, entry) + try: + m_time = datetime.datetime.fromtimestamp( + os.stat(entry_path).st_mtime + ) + current_time = datetime.datetime.now() + delta_time = timeutils.delta_seconds(m_time, current_time) + if delta_time < 0 or delta_time > self._timeout: + continue + with open(entry_path, 'rb') as fh: + details = self._load_and_validate(fh.read(), 'member') + # Decode member_id the same way _read_member_id does: + # a str member_id is stored with encoded=True and must + # be read back as str. + member_id: bytes | str = details['member_id'] + if details.get("encoded"): + member_id = cast(bytes, member_id).decode("utf-8") + caps = details.get("capabilities") + result[member_id] = caps + except OSError as e: + if e.errno != errno.ENOENT: + raise + return result + + fut = self._executor.submit(_do_get_members_with_capabilities) + return FileFutureResult(fut) + def delete_group( self, group_id: bytes ) -> coordination.CoordAsyncResult[None]: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tooz-9.0.1/tooz/drivers/memcached.py new/tooz-9.1.0/tooz/drivers/memcached.py --- old/tooz-9.0.1/tooz/drivers/memcached.py 2026-07-30 19:08:36.000000000 +0200 +++ new/tooz-9.1.0/tooz/drivers/memcached.py 2026-08-25 12:11:46.723394400 +0200 @@ -543,6 +543,24 @@ self._executor.submit(_get_member_capabilities) ) + def get_members_with_capabilities( + self, group_id: bytes + ) -> coordination.CoordAsyncResult[ + dict[bytes, coordination.Capabilities | None] + ]: + def _get_members_with_capabilities() -> dict[ + bytes, coordination.Capabilities | None + ]: + group_members = self._get_members(group_id) + return { + member_id: info[b'capabilities'] + for member_id, info in group_members.items() + } + + return MemcachedFutureResult( + self._executor.submit(_get_members_with_capabilities) + ) + def update_capabilities( self, group_id: bytes, capabilities: coordination.Capabilities | None ) -> coordination.CoordAsyncResult[None]: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tooz-9.0.1/tooz/drivers/redis.py new/tooz-9.1.0/tooz/drivers/redis.py --- old/tooz-9.0.1/tooz/drivers/redis.py 2026-07-30 19:08:36.000000000 +0200 +++ new/tooz-9.1.0/tooz/drivers/redis.py 2026-08-25 12:11:46.724394600 +0200 @@ -808,6 +808,63 @@ ) ) + def get_members_with_capabilities( + self, group_id: bytes + ) -> coordination.CoordinatorResult[ + dict[bytes, coordination.Capabilities | None] + ]: + encoded_group = self._encode_group_id(group_id) + + def _get_members_with_capabilities( + p: client.Pipeline, + ) -> dict[bytes, coordination.Capabilities | None]: + if not p.exists(encoded_group): + raise coordination.GroupNotCreated(group_id) + # HGETALL returns {field: value} — all members + capabilities + all_data = p.hgetall(encoded_group) + potential: dict[bytes, coordination.Capabilities | None] = {} + for raw_member_id, raw_caps in all_data.items(): + member_id = self._decode_member_id(raw_member_id) + if member_id == self.GROUP_EXISTS: + continue + potential[member_id] = self._loads(raw_caps) # type: ignore[arg-type] + + if not potential: + return {} + + # Liveness check — same pattern as get_members + gone_members: set[bytes] = set() + member_values = p.mget(map(self._encode_beat_id, potential.keys())) + for member_id, value in zip(potential.keys(), member_values): + if member_id == self._encode_member_id(self._member_id): + continue + if not value: + gone_members.add(member_id) + + if gone_members: + p.multi() + encoded_gone = list( + self._encode_member_id(m) for m in gone_members + ) + p.hdel(encoded_group, *encoded_gone) + p.execute() + + return { + m: caps + for m, caps in potential.items() + if m not in gone_members + } + + assert self._client is not None + return RedisFutureResult( + self._submit( + self._client.transaction, # type: ignore[arg-type] + _get_members_with_capabilities, # type: ignore[arg-type] + encoded_group, + value_from_callable=True, + ) + ) + def join_group( self, group_id: bytes, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tooz-9.0.1/tooz/drivers/zookeeper.py new/tooz-9.1.0/tooz/drivers/zookeeper.py --- old/tooz-9.0.1/tooz/drivers/zookeeper.py 2026-07-30 19:08:36.000000000 +0200 +++ new/tooz-9.1.0/tooz/drivers/zookeeper.py 2026-08-25 12:11:46.724394600 +0200 @@ -448,6 +448,53 @@ member_id=member_id, ) + def get_members_with_capabilities( + self, group_id: bytes + ) -> ZooAsyncResult[dict[bytes, coordination.Capabilities | None]]: + group_path = self._path_group(group_id) + async_result = self._coord.get_children_async(group_path) + + def _get_members_with_capabilities( + async_result: Any, + timeout: float | None, + /, + timeout_exception: type[Exception], + group_id: bytes, + ) -> dict[bytes, coordination.Capabilities | None]: + try: + members_ids = async_result.get(block=True, timeout=timeout) + except timeout_exception as e: + utils.raise_with_cause( + coordination.OperationTimedOut, str(e), cause=e + ) + except exceptions.NoNodeError: + raise coordination.GroupNotCreated(group_id) + except exceptions.ZookeeperError as e: + utils.raise_with_cause(tooz.ToozError, str(e), cause=e) + + result: dict[bytes, coordination.Capabilities | None] = {} + for m in members_ids: + member_id = m.encode('ascii') + member_path = self._path_member(group_id, member_id) + try: + data = self._coord.get(member_path)[0] + except exceptions.NoNodeError: + continue + except exceptions.ZookeeperError as e: + utils.raise_with_cause(tooz.ToozError, str(e), cause=e) + caps = utils.loads(data) if data else None + result[member_id] = cast( + coordination.Capabilities | None, caps + ) + return result + + return ZooAsyncResult( + async_result, + _get_members_with_capabilities, + timeout_exception=self._timeout_exception, + group_id=group_id, + ) + def get_member_info( self, group_id: bytes, member_id: bytes ) -> ZooAsyncResult[dict[str, Any]]: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tooz-9.0.1/tooz/tests/test_coordination.py new/tooz-9.1.0/tooz/tests/test_coordination.py --- old/tooz-9.0.1/tooz/tests/test_coordination.py 2026-07-30 19:08:36.000000000 +0200 +++ new/tooz-9.1.0/tooz/tests/test_coordination.py 2026-08-25 12:11:46.727394300 +0200 @@ -291,6 +291,53 @@ assert isinstance(capa, dict) self.assertEqual(capa['type'], caps['type']) + def test_get_members_with_capabilities(self): + self._coord.create_group(self.group_id).get() + caps = {"host": "10.0.0.1", "port": 8080} + self._coord.join_group(self.group_id, caps).get() + + result = self._coord.get_members_with_capabilities(self.group_id).get() + self.assertIn(self.member_id, result) + self.assertEqual(result[self.member_id], caps) + + def test_get_members_with_capabilities_multiple(self): + group_id = tests.get_random_uuid() + member_id2 = tests.get_random_uuid() + client2 = tooz.coordination.get_coordinator(self.url, member_id2) + client2.start() + self.addCleanup(client2.stop) + + self._coord.create_group(group_id).get() + self._coord.join_group(group_id, {"role": "primary"}).get() + client2.join_group(group_id, {"role": "secondary"}).get() + + result = self._coord.get_members_with_capabilities(group_id).get() + self.assertEqual(len(result), 2) + self.assertIn(self.member_id, result) + self.assertIn(member_id2, result) + self.assertEqual(result[self.member_id], {"role": "primary"}) + self.assertEqual(result[member_id2], {"role": "secondary"}) + + def test_get_members_with_capabilities_nonexistent_group(self): + result = self._coord.get_members_with_capabilities(self.group_id) + self.assertRaises( + tooz.coordination.GroupNotCreated, + result.get, + ) + + def test_get_members_with_capabilities_empty_group(self): + self._coord.create_group(self.group_id).get() + result = self._coord.get_members_with_capabilities(self.group_id).get() + self.assertEqual(result, {}) + + def test_get_members_with_capabilities_no_caps(self): + self._coord.create_group(self.group_id).get() + self._coord.join_group(self.group_id).get() # no capabilities + result = self._coord.get_members_with_capabilities(self.group_id).get() + self.assertIn(self.member_id, result) + # Drivers may return None or {} for absent capabilities + self.assertIn(result[self.member_id], (None, {})) + def test_get_member_capabilities_nonexistent_group(self): capa = self._coord.get_member_capabilities( self.group_id, self.member_id diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tooz-9.0.1/tooz.egg-info/PKG-INFO new/tooz-9.1.0/tooz.egg-info/PKG-INFO --- old/tooz-9.0.1/tooz.egg-info/PKG-INFO 2026-07-30 19:09:21.000000000 +0200 +++ new/tooz-9.1.0/tooz.egg-info/PKG-INFO 2026-08-25 12:12:41.749296200 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: tooz -Version: 9.0.1 +Version: 9.1.0 Summary: Coordination library for distributed systems. Author-email: OpenStack <[email protected]> License: Apache-2.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tooz-9.0.1/tooz.egg-info/pbr.json new/tooz-9.1.0/tooz.egg-info/pbr.json --- old/tooz-9.0.1/tooz.egg-info/pbr.json 2026-07-30 19:09:21.000000000 +0200 +++ new/tooz-9.1.0/tooz.egg-info/pbr.json 2026-08-25 12:12:41.747296300 +0200 @@ -1 +1 @@ -{"git_version": "100d17f", "is_release": true} \ No newline at end of file +{"git_version": "97ff7ff", "is_release": true} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tooz-9.0.1/tox.ini new/tooz-9.1.0/tox.ini --- old/tooz-9.0.1/tox.ini 2026-07-30 19:08:36.000000000 +0200 +++ new/tooz-9.1.0/tox.ini 2026-08-25 12:11:46.728394500 +0200 @@ -8,6 +8,15 @@ deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt +extras = + etcd3gw + redis + postgresql + mysql + zookeeper + memcached + ipc + kubernetes setenv = TOOZ_TEST_URLS = file:///tmp ipc:// zookeeper: TOOZ_TEST_DRIVERS = zookeeper @@ -57,9 +66,10 @@ sphinx-build -W -b html doc/source doc/build/html [testenv:releasenotes] +skip_install = true deps = {[testenv:docs]deps} commands = - sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html + sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:pep8] description =
