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

Attachment: signature.asc
Description: PGP signature

Reply via email to