Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add documentation, missing options and datetime support to expire commands #36

Merged
merged 7 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions tests/commands/asyncio/generic/test_expire.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from tests.execute_on_http import execute_on_http
from upstash_redis.asyncio import Redis

import datetime


@mark.asyncio
async def test(async_redis: Redis) -> None:
Expand All @@ -13,3 +15,21 @@ async def test(async_redis: Redis) -> None:
# Check if the expiry was correctly set.
await sleep(1)
assert await execute_on_http("EXISTS", "string_for_expire") == 0

@mark.asyncio
async def test_with_datetime(async_redis: Redis) -> None:
assert await async_redis.expire("string_for_expire_dt", datetime.timedelta(seconds=1)) is True

# Check if the expiry was correctly set.
await sleep(1)
assert await execute_on_http("EXISTS", "string_for_expire_dt") == 0

@mark.asyncio
async def test_xx(async_redis: Redis) -> None:
# Must fail since it does not have an expiry.
assert await async_redis.expire("string_without_expire", 1, xx=True) is False

@mark.asyncio
async def test_gt(async_redis: Redis) -> None:
# Must fail since it 1 is not greater than infinity.
assert await async_redis.expire("string_without_expire", 1, gt=True) is False
9 changes: 9 additions & 0 deletions tests/commands/asyncio/generic/test_expireat.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from asyncio import sleep
import datetime
from time import time

from pytest import mark
Expand All @@ -19,3 +20,11 @@ async def test(async_redis: Redis) -> None:

await sleep(2)
assert await execute_on_http("EXISTS", "string_for_expireat") == 0

@mark.asyncio
async def test_with_datetime(async_redis: Redis) -> None:
assert await async_redis.expireat("string_for_expireat_dt", datetime.datetime.now() + datetime.timedelta(seconds=1)) is True

# Check if the expiry was correctly set.
await sleep(1)
assert await execute_on_http("EXISTS", "string_for_expireat_dt") == 0
9 changes: 9 additions & 0 deletions tests/commands/asyncio/generic/test_pexpire.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from asyncio import sleep
import datetime

from pytest import mark

Expand All @@ -13,3 +14,11 @@ async def test(async_redis: Redis) -> None:
# Check if the expiry was correctly set.
await sleep(1)
assert await execute_on_http("EXISTS", "string_for_pexpire") == 0

@mark.asyncio
async def test_with_datetime(async_redis: Redis) -> None:
assert await async_redis.pexpire("string_for_pexpire_dt", datetime.timedelta(milliseconds=200)) is True

# Check if the expiry was correctly set.
await sleep(0.2)
assert await execute_on_http("EXISTS", "string_for_pexpire_dt") == 0
9 changes: 9 additions & 0 deletions tests/commands/asyncio/generic/test_pexpireat.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from asyncio import sleep
import datetime
from time import time

from pytest import mark
Expand All @@ -19,3 +20,11 @@ async def test(async_redis: Redis) -> None:

await sleep(2)
assert await execute_on_http("EXISTS", "string_for_pexpireat") == 0

@mark.asyncio
async def test_with_datetime(async_redis: Redis) -> None:
assert await async_redis.pexpireat("string_for_pexpireat_dt", datetime.datetime.now() + datetime.timedelta(milliseconds=200)) is True

# Check if the expiry was correctly set.
await sleep(0.2)
assert await execute_on_http("EXISTS", "string_for_pexpireat_dt") == 0
12 changes: 12 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@
"a",
"string_for_pexpireat",
"a",
# Strings for testing datetime with expire
"string_for_expire_dt",
"a",
"string_for_expireat_dt",
"a",
"string_for_pexpire_dt",
"a",
"string_for_pexpireat_dt",
"a",
"string_without_expire",
"a",
#Other expire stuff
"string_for_persist",
"a",
"string_for_ttl",
Expand Down
176 changes: 172 additions & 4 deletions upstash_redis/commands.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import datetime
from typing import Any, Awaitable, Dict, List, Literal, Optional, Tuple, Union

from upstash_redis.typing import FloatMinMaxT
Expand Down Expand Up @@ -165,22 +166,108 @@ def exists(self, *keys: str) -> ResponseT:

return self.execute(command)

def expire(self, key: str, seconds: int) -> ResponseT:
def expire(
self,
key: str,
seconds: Union[int, datetime.timedelta],
nx: bool = False,
xx: bool = False,
gt: bool = False,
lt: bool = False,
) -> ResponseT:
"""
Sets a timeout on a key in seconds.
After the timeout has expired, the key will automatically be deleted.

:param seconds: the timeout in seconds as int or datetime.timedelta object
:param nx: Set expiry only when the key has no expiry
:param xx: Set expiry only when the key has an existing expiry
:param gt: Set expiry only when the new expiry is greater than current one
:param lt: Set expiry only when the new expiry is less than current one

Example
```python
# With seconds
redis.set("mykey", "Hello")
redis.expire("mykey", 5)

assert redis.get("mykey") == "Hello"

time.sleep(5)

assert redis.get("mykey") is None

# With a timedelta
redis.set("mykey", "Hello")
redis.expire("mykey", datetime.timedelta(seconds=5))
```

See https://redis.io/commands/expire
"""

if isinstance(seconds, datetime.timedelta):
seconds = int(seconds.total_seconds())

command: List = ["EXPIRE", key, seconds]

if nx:
command.append("NX")
if xx:
command.append("XX")
if gt:
command.append("GT")
if lt:
command.append("LT")

return self.execute(command)

def expireat(self, key: str, unix_time_seconds: int) -> ResponseT:
def expireat(
self,
key: str,
unix_time_seconds: Union[int, datetime.datetime],
nx: bool = False,
xx: bool = False,
gt: bool = False,
lt: bool = False,
) -> ResponseT:
"""
Expires a key at a specific unix timestamp (seconds).
After the timeout has expired, the key will automatically be deleted.

:param seconds: the timeout in unix seconds timestamp as int or a datetime.datetime object.
:param nx: Set expiry only when the key has no expiry
:param xx: Set expiry only when the key has an existing expiry
:param gt: Set expiry only when the new expiry is greater than current one
:param lt: Set expiry only when the new expiry is less than current one

Example
```python
# With a datetime object
redis.set("mykey", "Hello")
redis.expireat("mykey", datetime.datetime.now() + datetime.timedelta(seconds=5))

# With a unix timestamp
redis.set("mykey", "Hello")
redis.expireat("mykey", int(time.time()) + 5)
```

See https://redis.io/commands/expireat
"""

if isinstance(unix_time_seconds, datetime.datetime):
unix_time_seconds = int(unix_time_seconds.timestamp())

command: List = ["EXPIREAT", key, unix_time_seconds]

if nx:
command.append("NX")
if xx:
command.append("XX")
if gt:
command.append("GT")
if lt:
command.append("LT")

return self.execute(command)

def keys(self, pattern: str) -> ResponseT:
Expand All @@ -201,22 +288,103 @@ def persist(self, key: str) -> ResponseT:

return self.execute(command)

def pexpire(self, key: str, milliseconds: int) -> ResponseT:
def pexpire(
self,
key: str,
milliseconds: Union[int, datetime.timedelta],
nx: bool = False,
xx: bool = False,
gt: bool = False,
lt: bool = False,
) -> ResponseT:
"""
Sets a timeout on a key in milliseconds.
After the timeout has expired, the key will automatically be deleted.

:param milliseconds: the timeout in milliseconds as int or datetime.timedelta.
:param nx: Set expiry only when the key has no expiry
:param xx: Set expiry only when the key has an existing expiry
:param gt: Set expiry only when the new expiry is greater than current one
:param lt: Set expiry only when the new expiry is less than current one

Example
```python
# With milliseconds
redis.set("mykey", "Hello")
redis.expire("mykey", 500)

# With a timedelta
redis.set("mykey", "Hello")
redis.expire("mykey", datetime.timedelta(milliseconds=500))
```

See https://redis.io/commands/pexpire
"""

if isinstance(milliseconds, datetime.timedelta):
# Total seconds returns float, so this is OK.
milliseconds = int(milliseconds.total_seconds() * 1000)

command: List = ["PEXPIRE", key, milliseconds]

if nx:
command.append("NX")
if xx:
command.append("XX")
if gt:
command.append("GT")
if lt:
command.append("LT")

return self.execute(command)

def pexpireat(self, key: str, unix_time_milliseconds: int) -> ResponseT:
def pexpireat(
self,
key: str,
unix_time_milliseconds: Union[int, datetime.datetime],
nx: bool = False,
xx: bool = False,
gt: bool = False,
lt: bool = False,
) -> ResponseT:
"""
Expires a key at a specific unix timestamp (milliseconds).
After the timeout has expired, the key will automatically be deleted.

:param unix_time_milliseconds: the timeout in unix miliseconds timestamp as int or a datetime.datetime object.
:param nx: Set expiry only when the key has no expiry
:param xx: Set expiry only when the key has an existing expiry
:param gt: Set expiry only when the new expiry is greater than current one
:param lt: Set expiry only when the new expiry is less than current one

Example
```python
# With a unix timestamp
redis.set("mykey", "Hello")
redis.pexpireat("mykey", int(time.time() * 1000) )

# With a datetime object
redis.set("mykey", "Hello")
redis.pexpireat("mykey", datetime.datetime.now() + datetime.timedelta(seconds=5))
```

See https://redis.io/commands/pexpireat
"""

if isinstance(unix_time_milliseconds, datetime.datetime):
unix_time_milliseconds = int(unix_time_milliseconds.timestamp() * 1000)

command: List = ["PEXPIREAT", key, unix_time_milliseconds]

if nx:
command.append("NX")
if xx:
command.append("XX")
if gt:
command.append("GT")
if lt:
command.append("LT")

return self.execute(command)

def pttl(self, key: str) -> ResponseT:
Expand Down
Loading
Loading