Repository: redis/redis-py

Issue:
Add support for PEXPIREAT redis-7 options [NX|XX|GT|LT]



Discussion:

Resolving patch:
diff --git a/redis/commands/core.py b/redis/commands/core.py
--- a/redis/commands/core.py
+++ b/redis/commands/core.py
@@ -1787,18 +1787,41 @@ def pexpire(self, name: KeyT, time: ExpiryT) -> ResponseT:
             time = int(time.total_seconds() * 1000)
         return self.execute_command("PEXPIRE", name, time)
 
-    def pexpireat(self, name: KeyT, when: AbsExpiryT) -> ResponseT:
+    def pexpireat(
+        self,
+        name: KeyT,
+        when: AbsExpiryT,
+        nx: bool = False,
+        xx: bool = False,
+        gt: bool = False,
+        lt: bool = False,
+    ) -> ResponseT:
         """
-        Set an expire flag on key ``name``. ``when`` can be represented
-        as an integer representing unix time in milliseconds (unix time * 1000)
-        or a Python datetime object.
+        Set an expire flag on key ``name`` with given ``option``. ``when``
+        can be represented as an integer representing unix time in
+        milliseconds (unix time * 1000) or a Python datetime object.
+
+        Valid options are:
+            NX -> Set expiry only when the key has no expiry
+            XX -> Set expiry only when the key has an existing expiry
+            GT -> Set expiry only when the new expiry is greater than current one
+            LT -> Set expiry only when the new expiry is less than current one
 
         For more information check https://redis.io/commands/pexpireat
         """
         if isinstance(when, datetime.datetime):
             ms = int(when.microsecond / 1000)
             when = int(time.mktime(when.timetuple())) * 1000 + ms
-        return self.execute_command("PEXPIREAT", name, when)
+        exp_option = list()
+        if nx:
+            exp_option.append("NX")
+        if xx:
+            exp_option.append("XX")
+        if gt:
+            exp_option.append("GT")
+        if lt:
+            exp_option.append("LT")
+        return self.execute_command("PEXPIREAT", name, when, *exp_option)
 
     def pexpiretime(self, key: str) -> int:
         """


Test patch:
diff --git a/tests/test_commands.py b/tests/test_commands.py
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -1254,6 +1254,41 @@ def test_pexpireat_unixtime(self, r):
         assert r.pexpireat("a", expire_at_seconds) is True
         assert 0 < r.pttl("a") <= 61000
 
+    @skip_if_server_version_lt("7.0.0")
+    def test_pexpireat_option_nx(self, r):
+        assert r.set("key", "val") is True
+        expire_at = redis_server_time(r) + datetime.timedelta(minutes=1)
+        assert r.pexpireat("key", expire_at, nx=True) is True
+        assert r.pexpireat("key", expire_at, nx=True) is False
+
+    @skip_if_server_version_lt("7.0.0")
+    def test_pexpireat_option_xx(self, r):
+        assert r.set("key", "val") is True
+        expire_at = redis_server_time(r) + datetime.timedelta(minutes=1)
+        assert r.pexpireat("key", expire_at, xx=True) is False
+        assert r.pexpireat("key", expire_at) is True
+        assert r.pexpireat("key", expire_at, xx=True) is True
+
+    @skip_if_server_version_lt("7.0.0")
+    def test_pexpireat_option_gt(self, r):
+        assert r.set("key", "val") is True
+        expire_at = redis_server_time(r) + datetime.timedelta(minutes=2)
+        assert r.pexpireat("key", expire_at) is True
+        expire_at = redis_server_time(r) + datetime.timedelta(minutes=1)
+        assert r.pexpireat("key", expire_at, gt=True) is False
+        expire_at = redis_server_time(r) + datetime.timedelta(minutes=3)
+        assert r.pexpireat("key", expire_at, gt=True) is True
+
+    @skip_if_server_version_lt("7.0.0")
+    def test_pexpireat_option_lt(self, r):
+        assert r.set("key", "val") is True
+        expire_at = redis_server_time(r) + datetime.timedelta(minutes=2)
+        assert r.pexpireat("key", expire_at) is True
+        expire_at = redis_server_time(r) + datetime.timedelta(minutes=3)
+        assert r.pexpireat("key", expire_at, lt=True) is False
+        expire_at = redis_server_time(r) + datetime.timedelta(minutes=1)
+        assert r.pexpireat("key", expire_at, lt=True) is True
+
     @skip_if_server_version_lt("7.0.0")
     def test_pexpiretime(self, r):
         r.set("a", "foo")