Hi! I've made an NMU for python-aioredlock, applying an upstream patch [1] that gets rid of the python-aioredis dependency.
Here's the debdiff of the uploaded package. Happy hacking, [1]: https://github.com/joanvila/aioredlock/pull/111 --"Email is a wonderful thing for people whose role in life is to be on top of things. But not for me; my role is to be on the bottom of things. What I do takes long hours of studying and uninterruptible concentration."
-- Donald Knuth Saludos /\/\ /\ >< `/
diff --git a/debian/changelog b/debian/changelog index 518bafa..f165e56 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +python-aioredlock (0.7.3-3.1) unstable; urgency=medium + + * Non-maintainer upload. + * Migrate from aioredis to redis. Closes: #1074327 + * Drop patch python3.12 and python3-packaging dependency, superseded. + + -- Maximiliano Curia <[email protected]> Mon, 05 Oct 2026 16:44:05 +0200 + python-aioredlock (0.7.3-3) unstable; urgency=medium * Add patch python3.12: fix FTBFS, compatibility with Python 3.12. diff --git a/debian/control b/debian/control index 0a720f4..ee21179 100644 --- a/debian/control +++ b/debian/control @@ -2,10 +2,9 @@ Rules-Requires-Root: no Standards-Version: 4.6.2.0 Build-Depends: debhelper-compat (= 13), dh-sequence-python3, - python3-aioredis (<< 2.0.0), python3-all, python3-attr (>= 17.4.0), - python3-packaging, + python3-redis, python3-setuptools Testsuite: autopkgtest-pkg-python Source: python-aioredlock @@ -17,7 +16,7 @@ Vcs-Git: https://salsa.debian.org/jelmer/python-aioredlock.git -b debian/main Vcs-Browser: https://salsa.debian.org/jelmer/python-aioredlock/tree/debian/main Package: python3-aioredlock -Depends: ${python3:Depends}, ${misc:Depends}, python3-packaging +Depends: ${python3:Depends}, ${misc:Depends} Architecture: all Description: Python asyncio redis redlock implementation This Python module provides an async implementation of the redis Redlock diff --git a/debian/patches/python3.12 b/debian/patches/python3.12 deleted file mode 100644 index 00350c6..0000000 --- a/debian/patches/python3.12 +++ /dev/null @@ -1,22 +0,0 @@ -diff --git a/aioredlock/redis.py b/aioredlock/redis.py -index 27958c3..d38b644 100644 ---- a/aioredlock/redis.py -+++ b/aioredlock/redis.py -@@ -2,7 +2,7 @@ import asyncio - import logging - import re - import time --from distutils.version import StrictVersion -+from packaging.version import parse as parse_version - from itertools import groupby - - import aioredis -@@ -110,7 +110,7 @@ class Instance: - aioredis.create_pool(*args, **kwargs) - """ - -- if StrictVersion(aioredis.__version__) >= StrictVersion('1.0.0'): # pragma no cover -+ if parse_version(aioredis.__version__) >= parse_version('1.0.0'): # pragma no cover - return await aioredis.create_redis_pool(*args, **kwargs) - else: # pragma no cover - return await aioredis.create_pool(*args, **kwargs) diff --git a/debian/patches/redis.patch b/debian/patches/redis.patch new file mode 100644 index 0000000..f2457e0 --- /dev/null +++ b/debian/patches/redis.patch @@ -0,0 +1,407 @@ +From: Shehab Abdel-Salam <[email protected]> +Date: Wed, 22 Jan 2025 15:55:41 +0000 +Subject: WIP: aioredlock supports redis-py + +Origin: backport, https://github.com/joanvila/aioredlock/pull/111 +Bug-Debian: https://bugs.debian.org/1074327 + +Index: python-aioredlock/aioredlock.egg-info/requires.txt +=================================================================== +--- python-aioredlock.orig/aioredlock.egg-info/requires.txt ++++ python-aioredlock/aioredlock.egg-info/requires.txt +@@ -1,4 +1,4 @@ +-aioredis<2.0.0 ++redis>=5.0.0 + attrs>=17.4.0 + + [cicd] +Index: python-aioredlock/aioredlock/redis.py +=================================================================== +--- python-aioredlock.orig/aioredlock/redis.py ++++ python-aioredlock/aioredlock/redis.py +@@ -1,11 +1,13 @@ + import asyncio ++import functools + import logging + import re + import time +-from distutils.version import StrictVersion + from itertools import groupby ++from typing import Optional + +-import aioredis ++from redis.asyncio import ConnectionPool, Redis as AIORedis ++from redis.exceptions import RedisError, ResponseError + + from aioredlock.errors import LockError, LockAcquiringError, LockRuntimeError + from aioredlock.sentinel import Sentinel +@@ -64,6 +66,16 @@ class Instance: + return redis.error_reply('ERROR') + end""" + ++ @staticmethod ++ def ensure_connection(func): ++ @functools.wraps(func) ++ async def wrapper(self, *args, **kwargs): ++ # Ensure connection is established before proceeding ++ if self._client is None: ++ await self.connect() ++ return await func(self, *args, **kwargs) ++ return wrapper ++ + def __init__(self, connection): + """ + Redis instance constructor +@@ -71,9 +83,7 @@ class Instance: + Constructor takes single argument - a redis host address + The address can be one of the following: + * a dict - {'host': 'localhost', 'port': 6379, +- 'db': 0, 'password': 'pass'} +- all keys except host and port will be passed as kwargs to +- the aioredis.create_redis_pool(); ++ 'db': 0, 'password': 'pass'}; + * an aioredlock.redis.Sentinel object; + * a Redis URI - "redis://host:6379/0?encoding=utf-8"; + * a (host, port) tuple - ('localhost', 6379); +@@ -85,7 +95,7 @@ class Instance: + + self.connection = connection + +- self._pool = None ++ self._client: Optional[AIORedis] = None + self._lock = asyncio.Lock() + + self.set_lock_script_sha1 = None +@@ -101,21 +111,13 @@ class Instance: + return "<%s(connection='%s'>" % (self.__class__.__name__, connection_details) + + @staticmethod +- async def _create_redis_pool(*args, **kwargs): +- """ +- Adapter to support both aioredis-0.3.0 and aioredis-1.0.0 +- For aioredis-1.0.0 and later calls: +- aioredis.create_redis_pool(*args, **kwargs) +- For aioredis-0.3.0 calls: +- aioredis.create_pool(*args, **kwargs) +- """ ++ async def _create_redis(*args, **kwargs) -> AIORedis: ++ if args[0] is None: ++ return AIORedis(**kwargs) + +- if StrictVersion(aioredis.__version__) >= StrictVersion('1.0.0'): # pragma no cover +- return await aioredis.create_redis_pool(*args, **kwargs) +- else: # pragma no cover +- return await aioredis.create_pool(*args, **kwargs) ++ return AIORedis.from_url(*args, **kwargs) + +- async def _register_scripts(self, redis): ++ async def _register_scripts(self): + tasks = [] + for script in [ + self.SET_LOCK_SCRIPT, +@@ -123,63 +125,67 @@ class Instance: + self.GET_LOCK_TTL_SCRIPT, + ]: + script = re.sub(r'^\s+', '', script, flags=re.M).strip() +- tasks.append(redis.script_load(script)) ++ tasks.append(self._client.script_load(script)) ++ results = await asyncio.gather(*tasks) + ( + self.set_lock_script_sha1, + self.unset_lock_script_sha1, + self.get_lock_ttl_script_sha1, +- ) = (r.decode() if isinstance(r, bytes) else r for r in await asyncio.gather(*tasks)) ++ ) = (r if isinstance(r, str) else r.decode('utf-8') for r in results) + +- async def connect(self): ++ async def connect(self) -> AIORedis: + """ + Get an connection for the self instance + """ +- address, redis_kwargs = (), {} ++ address, redis_kwargs = None, {} + + if isinstance(self.connection, Sentinel): +- self._pool = await self.connection.get_master() ++ self._client = self.connection.get_master() + elif isinstance(self.connection, dict): +- # a dict like {'host': 'localhost', 'port': 6379, +- # 'db': 0, 'password': 'pass'} +- kwargs = self.connection.copy() +- address = ( +- kwargs.pop('host', 'localhost'), +- kwargs.pop('port', 6379) +- ) +- redis_kwargs = kwargs +- elif isinstance(self.connection, aioredis.Redis): +- self._pool = self.connection ++ assert 'host' in self.connection, 'Host is not specified' ++ redis_kwargs = self.connection ++ elif isinstance(self.connection, ConnectionPool): ++ conn_kwargs = self.connection.connection_kwargs ++ url = f"redis://{conn_kwargs['host']}:{conn_kwargs['port']}/{conn_kwargs['db']}?encoding=utf-8" ++ self._client = AIORedis.from_url(url) ++ elif isinstance(self.connection, tuple): ++ redis_kwargs = { ++ 'host': self.connection[0], ++ 'port': self.connection[1], ++ 'db': self.connection[2] if len(self.connection) > 2 else 0, ++ 'password': self.connection[3] if len(self.connection) > 3 else None, ++ } ++ elif isinstance(self.connection, AIORedis): ++ self._client = self.connection + else: +- # a tuple or list ('localhost', 6379) +- # a string "redis://host:6379/0?encoding=utf-8" or +- # a unix domain socket path "/path/to/redis.sock" + address = self.connection + +- if self._pool is None: +- if 'minsize' not in redis_kwargs: +- redis_kwargs['minsize'] = 1 +- if 'maxsize' not in redis_kwargs: +- redis_kwargs['maxsize'] = 100 ++ if self._client is None: ++ redis_kwargs['max_connections'] = redis_kwargs.get('max_connections', 100) + async with self._lock: +- if self._pool is None: ++ if self._client is None: + self.log.debug('Connecting %s', repr(self)) +- self._pool = await self._create_redis_pool(address, **redis_kwargs) ++ self._client = await self._create_redis(address, **redis_kwargs) + + if self.set_lock_script_sha1 is None or self.unset_lock_script_sha1 is None: +- with await self._pool as redis: +- await self._register_scripts(redis) ++ await self._register_scripts() + +- return await self._pool ++ return self._client + +- async def close(self): ++ async def aclose(self): + """ + Closes connection and resets pool + """ +- if self._pool is not None and not isinstance(self.connection, aioredis.Redis): +- self._pool.close() +- await self._pool.wait_closed() +- self._pool = None ++ if self._client is not None and not isinstance(self.connection, AIORedis): ++ try: ++ await self._client.aclose() ++ except AttributeError: ++ await self._client.close() ++ self._client = None + ++ close = aclose ++ ++ @ensure_connection + async def set_lock(self, resource, lock_identifier, lock_timeout, register_scripts=False): + """ + Lock this instance and set lock expiration time to lock_timeout +@@ -192,21 +198,22 @@ class Instance: + lock_timeout_ms = int(lock_timeout * 1000) + + try: +- with await self.connect() as redis: +- if register_scripts is True: +- await self._register_scripts(redis) +- await redis.evalsha( +- self.set_lock_script_sha1, +- keys=[resource], +- args=[lock_identifier, lock_timeout_ms] +- ) +- except aioredis.errors.ReplyError as exc: # script fault ++ if register_scripts is True: ++ await self._register_scripts() ++ await self._client.evalsha( ++ self.set_lock_script_sha1, ++ 1, ++ resource, ++ lock_identifier, ++ lock_timeout_ms, ++ ) ++ except ResponseError as exc: # script fault + if exc.args[0].startswith('NOSCRIPT'): + return await self.set_lock(resource, lock_identifier, lock_timeout, register_scripts=True) + self.log.debug('Can not set lock "%s" on %s', + resource, repr(self)) + raise LockAcquiringError('Can not set lock') from exc +- except (aioredis.errors.RedisError, OSError) as exc: ++ except (RedisError, OSError) as exc: + self.log.error('Can not set lock "%s" on %s: %s', + resource, repr(self), repr(exc)) + raise LockRuntimeError('Can not set lock') from exc +@@ -221,6 +228,7 @@ class Instance: + else: + self.log.debug('Lock "%s" is set on %s', resource, repr(self)) + ++ @ensure_connection + async def get_lock_ttl(self, resource, lock_identifier, register_scripts=False): + """ + Fetch this instance and set lock expiration time to lock_timeout +@@ -230,21 +238,21 @@ class Instance: + :raises: LockError if lock is not available + """ + try: +- with await self.connect() as redis: +- if register_scripts is True: +- await self._register_scripts(redis) +- ttl = await redis.evalsha( +- self.get_lock_ttl_script_sha1, +- keys=[resource], +- args=[lock_identifier] +- ) +- except aioredis.errors.ReplyError as exc: # script fault ++ if register_scripts is True: ++ await self._register_scripts() ++ ttl = await self._client.evalsha( ++ self.get_lock_ttl_script_sha1, ++ 1, ++ resource, ++ lock_identifier, ++ ) ++ except ResponseError as exc: # script fault + if exc.args[0].startswith('NOSCRIPT'): + return await self.get_lock_ttl(resource, lock_identifier, register_scripts=True) + self.log.debug('Can not get lock "%s" on %s', + resource, repr(self)) + raise LockAcquiringError('Can not get lock') from exc +- except (aioredis.errors.RedisError, OSError) as exc: ++ except (RedisError, OSError) as exc: + self.log.error('Can not get lock "%s" on %s: %s', + resource, repr(self), repr(exc)) + raise LockRuntimeError('Can not get lock') from exc +@@ -260,6 +268,7 @@ class Instance: + self.log.debug('Lock "%s" with TTL %s is on %s', resource, ttl, repr(self)) + return ttl + ++ @ensure_connection + async def unset_lock(self, resource, lock_identifier, register_scripts=False): + """ + Unlock this instance +@@ -268,21 +277,21 @@ class Instance: + :raises: LockError if the lock resource acquired with different lock_identifier + """ + try: +- with await self.connect() as redis: +- if register_scripts is True: +- await self._register_scripts(redis) +- await redis.evalsha( +- self.unset_lock_script_sha1, +- keys=[resource], +- args=[lock_identifier] +- ) +- except aioredis.errors.ReplyError as exc: # script fault ++ if register_scripts is True: ++ await self._register_scripts() ++ await self._client.evalsha( ++ self.unset_lock_script_sha1, ++ 1, ++ resource, ++ lock_identifier, ++ ) ++ except ResponseError as exc: # script fault + if exc.args[0].startswith('NOSCRIPT'): + return await self.unset_lock(resource, lock_identifier, register_scripts=True) + self.log.debug('Can not unset lock "%s" on %s', + resource, repr(self)) + raise LockAcquiringError('Can not unset lock') from exc +- except (aioredis.errors.RedisError, OSError) as exc: ++ except (RedisError, OSError) as exc: + self.log.error('Can not unset lock "%s" on %s: %s', + resource, repr(self), repr(exc)) + raise LockRuntimeError('Can not unset lock') from exc +@@ -297,6 +306,7 @@ class Instance: + else: + self.log.debug('Lock "%s" is unset on %s', resource, repr(self)) + ++ @ensure_connection + async def is_locked(self, resource): + """ + Checks if the resource is locked by any redlock instance. +@@ -305,8 +315,7 @@ class Instance: + :returns: True if locked else False + """ + +- with await self.connect() as redis: +- lock_identifier = await redis.get(resource) ++ lock_identifier = await self._client.get(resource) + if lock_identifier: + return True + else: +@@ -371,7 +380,7 @@ class Redis: + i.get_lock_ttl(resource, lock_identifier) for + i in self.instances + ], return_exceptions=True) +- successful_list = [s for s in successes if not isinstance(s, Exception)] ++ successful_list = [s for s in successes if not isinstance(s, BaseException)] + # should check if all the value are approx. the same with math.isclose... + locked = len(successful_list) >= int(len(self.instances) / 2) + 1 + success = all_equal(successful_list) and locked +@@ -441,5 +450,5 @@ class Redis: + if self.instances: + coros = [] + while self.instances: +- coros.append(self.instances.pop().close()) ++ coros.append(self.instances.pop().aclose()) + await asyncio.gather(*(coros)) +Index: python-aioredlock/aioredlock/sentinel.py +=================================================================== +--- python-aioredlock.orig/aioredlock/sentinel.py ++++ python-aioredlock/aioredlock/sentinel.py +@@ -2,7 +2,7 @@ import re + import ssl + import urllib.parse + +-import aioredis.sentinel ++from redis.asyncio import Sentinel as RedisSentinel, Redis as AIORedis + + + class SentinelConfigError(Exception): +@@ -92,24 +92,23 @@ class Sentinel: + if self.master is None: + raise SentinelConfigError('Master name required for sentinel to be configured') + +- kwargs['minsize'] = 1 if 'minsize' not in kwargs else int(kwargs['minsize']) +- kwargs['maxsize'] = 100 if 'maxsize' not in kwargs else int(kwargs['maxsize']) ++ kwargs['max_connections'] = int(kwargs.get('max_connections', 100)) + + self.connection = address + self.redis_kwargs = kwargs + +- async def get_sentinel(self): ++ def get_sentinel(self): + ''' +- Retrieve sentinel object from aioredis. ++ Retrieve sentinel object from redis. + ''' +- return await aioredis.sentinel.create_sentinel( ++ return RedisSentinel( + sentinels=self.connection, + **self.redis_kwargs, + ) + +- async def get_master(self): ++ def get_master(self) -> AIORedis: + ''' + Get ``Redis`` instance for specified ``master`` + ''' +- sentinel = await self.get_sentinel() +- return await sentinel.master_for(self.master) ++ sentinel = self.get_sentinel() ++ return sentinel.master_for(self.master) +Index: python-aioredlock/setup.py +=================================================================== +--- python-aioredlock.orig/setup.py ++++ python-aioredlock/setup.py +@@ -42,7 +42,7 @@ setup( + packages=find_packages(), + + python_requires='>=3.6', +- install_requires=['aioredis<2.0.0', 'attrs >= 17.4.0'], ++ install_requires=['redis>=5.0.0', 'attrs >= 17.4.0'], + extras_require={ + 'test': ['pytest==6.1.0', 'pytest-asyncio', 'pytest-mock', 'pytest-cov', 'flake8'], + 'cicd': ['codecov'], diff --git a/debian/patches/series b/debian/patches/series index 94badfe..0891225 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1 +1 @@ -python3.12 +redis.patch
signature.asc
Description: PGP signature

