Skip to content

Commit 28c3323

Browse files
authored
Make expire, pexpire, expireat and persist return the redis client value (#565)
* Make expire, pexpire, expireat return the value redis returns for these actions. True/1 if the key was present in redis, False/0 if not present * Add pexpire_at. Cast values to bool for consistency
1 parent 5bf67ab commit 28c3323

File tree

7 files changed

+109
-18
lines changed

7 files changed

+109
-18
lines changed

README.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,22 +393,47 @@ specify a new expiration timeout using the ``persist`` and ``expire`` methods:
393393
>>> cache.ttl("foo")
394394
22
395395
>>> cache.persist("foo")
396+
True
396397
>>> cache.ttl("foo")
397398
None
398399
399400
.. code-block:: pycon
400401
401402
>>> cache.set("foo", "bar", timeout=22)
402403
>>> cache.expire("foo", timeout=5)
404+
True
403405
>>> cache.ttl("foo")
404406
5
405407
408+
The ``expire_at`` method can be used to make the key expire at a specific moment in time.
409+
410+
.. code-block:: pycon
411+
412+
>>> cache.set("foo", "bar", timeout=22)
413+
>>> cache.expire_at("foo", datetime.now() + timedelta(hours=1))
414+
True
415+
>>> cache.ttl("foo")
416+
3600
417+
418+
The ``pexpire_at`` method can be used to make the key expire at a specific moment in time with milliseconds precision:
419+
420+
.. code-block:: pycon
421+
422+
>>> cache.set("foo", "bar", timeout=22)
423+
>>> cache.pexpire_at("foo", datetime.now() + timedelta(milliseconds=900, hours=1))
424+
True
425+
>>> cache.ttl("foo")
426+
3601
427+
>>> cache.pttl("foo")
428+
3600900
429+
406430
The ``pexpire`` method can be used to provide millisecond precision:
407431

408432
.. code-block:: pycon
409433
410434
>>> cache.set("foo", "bar", timeout=22)
411435
>>> cache.pexpire("foo", timeout=5500)
436+
True
412437
>>> cache.pttl("foo")
413438
5500
414439

changelog.d/564.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Make expire, pexpire, expireat and persist return the redis client value

changelog.d/564.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add pexpireat to allow setting 'expire at' with millisecond precision.

django_redis/cache.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,10 @@ def expire_at(self, *args, **kwargs):
168168
def pexpire(self, *args, **kwargs):
169169
return self.client.pexpire(*args, **kwargs)
170170

171+
@omit_exception
172+
def pexpire_at(self, *args, **kwargs):
173+
return self.client.pexpire_at(*args, **kwargs)
174+
171175
@omit_exception
172176
def lock(self, *args, **kwargs):
173177
return self.client.lock(*args, **kwargs)

django_redis/client/default.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -266,46 +266,63 @@ def get(
266266

267267
def persist(
268268
self, key: Any, version: Optional[int] = None, client: Optional[Redis] = None
269-
) -> None:
269+
) -> bool:
270270
if client is None:
271271
client = self.get_client(write=True)
272272

273273
key = self.make_key(key, version=version)
274274

275-
if client.exists(key):
276-
client.persist(key)
275+
return client.persist(key)
277276

278277
def expire(
279278
self,
280279
key: Any,
281280
timeout,
282281
version: Optional[int] = None,
283282
client: Optional[Redis] = None,
284-
) -> None:
283+
) -> bool:
285284
if client is None:
286285
client = self.get_client(write=True)
287286

288287
key = self.make_key(key, version=version)
289288

290-
if client.exists(key):
291-
client.expire(key, timeout)
289+
return client.expire(key, timeout)
292290

293-
def pexpire(self, key, timeout, version=None, client=None):
291+
def pexpire(self, key, timeout, version=None, client=None) -> bool:
292+
if client is None:
293+
client = self.get_client(write=True)
294+
295+
key = self.make_key(key, version=version)
296+
297+
# Temporary casting until https://github.com/redis/redis-py/issues/1664
298+
# is fixed.
299+
return bool(client.pexpire(key, timeout))
300+
301+
def pexpire_at(
302+
self,
303+
key: Any,
304+
when: Union[datetime, int],
305+
version: Optional[int] = None,
306+
client: Optional[Redis] = None,
307+
) -> bool:
308+
"""
309+
Set an expire flag on a ``key`` to ``when``, which can be represented
310+
as an integer indicating unix time or a Python datetime object.
311+
"""
294312
if client is None:
295313
client = self.get_client(write=True)
296314

297315
key = self.make_key(key, version=version)
298316

299-
if client.exists(key):
300-
client.pexpire(key, timeout)
317+
return bool(client.pexpireat(key, when))
301318

302319
def expire_at(
303320
self,
304321
key: Any,
305322
when: Union[datetime, int],
306323
version: Optional[int] = None,
307324
client: Optional[Redis] = None,
308-
) -> None:
325+
) -> bool:
309326
"""
310327
Set an expire flag on a ``key`` to ``when``, which can be represented
311328
as an integer indicating unix time or a Python datetime object.
@@ -315,8 +332,7 @@ def expire_at(
315332

316333
key = self.make_key(key, version=version)
317334

318-
if client.exists(key):
319-
client.expireat(key, when)
335+
return client.expireat(key, when)
320336

321337
def lock(
322338
self,

django_redis/client/sharded.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,18 @@ def pexpire(self, key, timeout, version=None, client=None):
171171

172172
return super().pexpire(key=key, timeout=timeout, version=version, client=client)
173173

174+
def pexpire_at(self, key, when: Union[datetime, int], version=None, client=None):
175+
"""
176+
Set an expire flag on a ``key`` to ``when`` on a shard client.
177+
``when`` which can be represented as an integer indicating unix
178+
time or a Python datetime object.
179+
"""
180+
if client is None:
181+
key = self.make_key(key, version=version)
182+
client = self.get_server(key)
183+
184+
return super().pexpire_at(key=key, when=when, version=version, client=client)
185+
174186
def expire_at(self, key, when: Union[datetime, int], version=None, client=None):
175187
"""
176188
Set an expire flag on a ``key`` to ``when`` on a shard client.

tests/test_backend.py

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -577,47 +577,79 @@ def test_pttl(self, cache: RedisCache):
577577

578578
def test_persist(self, cache: RedisCache):
579579
cache.set("foo", "bar", timeout=20)
580-
cache.persist("foo")
580+
assert cache.persist("foo") is True
581581

582582
ttl = cache.ttl("foo")
583583
assert ttl is None
584+
assert cache.persist("not-existent-key") is False
584585

585586
def test_expire(self, cache: RedisCache):
586587
cache.set("foo", "bar", timeout=None)
587-
cache.expire("foo", 20)
588+
assert cache.expire("foo", 20) is True
588589
ttl = cache.ttl("foo")
589590
assert pytest.approx(ttl) == 20
591+
assert cache.expire("not-existent-key", 20) is False
590592

591593
def test_pexpire(self, cache: RedisCache):
592594
cache.set("foo", "bar", timeout=None)
593-
cache.pexpire("foo", 20500)
595+
assert cache.pexpire("foo", 20500) is True
594596
ttl = cache.pttl("foo")
595597
# delta is set to 10 as precision error causes tests to fail
596598
assert pytest.approx(ttl, 10) == 20500
599+
assert cache.pexpire("not-existent-key", 20500) is False
600+
601+
def test_pexpire_at(self, cache: RedisCache):
602+
603+
# Test settings expiration time 1 hour ahead by datetime.
604+
cache.set("foo", "bar", timeout=None)
605+
expiration_time = datetime.datetime.now() + timedelta(hours=1)
606+
assert cache.pexpire_at("foo", expiration_time) is True
607+
ttl = cache.pttl("foo")
608+
assert pytest.approx(ttl, 10) == timedelta(hours=1).total_seconds()
609+
610+
# Test settings expiration time 1 hour ahead by Unix timestamp.
611+
cache.set("foo", "bar", timeout=None)
612+
expiration_time = datetime.datetime.now() + timedelta(hours=2)
613+
assert cache.pexpire_at("foo", int(expiration_time.timestamp() * 1000)) is True
614+
ttl = cache.pttl("foo")
615+
assert pytest.approx(ttl, 10) == timedelta(hours=2).total_seconds() * 1000
616+
617+
# Test settings expiration time 1 hour in past, which effectively
618+
# deletes the key.
619+
expiration_time = datetime.datetime.now() - timedelta(hours=2)
620+
assert cache.pexpire_at("foo", expiration_time) is True
621+
value = cache.get("foo")
622+
assert value is None
623+
624+
expiration_time = datetime.datetime.now() + timedelta(hours=2)
625+
assert cache.pexpire_at("not-existent-key", expiration_time) is False
597626

598627
def test_expire_at(self, cache: RedisCache):
599628

600629
# Test settings expiration time 1 hour ahead by datetime.
601630
cache.set("foo", "bar", timeout=None)
602631
expiration_time = datetime.datetime.now() + timedelta(hours=1)
603-
cache.expire_at("foo", expiration_time)
632+
assert cache.expire_at("foo", expiration_time) is True
604633
ttl = cache.ttl("foo")
605634
assert pytest.approx(ttl, 1) == timedelta(hours=1).total_seconds()
606635

607636
# Test settings expiration time 1 hour ahead by Unix timestamp.
608637
cache.set("foo", "bar", timeout=None)
609638
expiration_time = datetime.datetime.now() + timedelta(hours=2)
610-
cache.expire_at("foo", int(expiration_time.timestamp()))
639+
assert cache.expire_at("foo", int(expiration_time.timestamp())) is True
611640
ttl = cache.ttl("foo")
612641
assert pytest.approx(ttl, 1) == timedelta(hours=1).total_seconds() * 2
613642

614643
# Test settings expiration time 1 hour in past, which effectively
615644
# deletes the key.
616645
expiration_time = datetime.datetime.now() - timedelta(hours=2)
617-
cache.expire_at("foo", expiration_time)
646+
assert cache.expire_at("foo", expiration_time) is True
618647
value = cache.get("foo")
619648
assert value is None
620649

650+
expiration_time = datetime.datetime.now() + timedelta(hours=2)
651+
assert cache.expire_at("not-existent-key", expiration_time) is False
652+
621653
def test_lock(self, cache: RedisCache):
622654
lock = cache.lock("foobar")
623655
lock.acquire(blocking=True)

0 commit comments

Comments
 (0)