Skip to content

Commit 10db42b

Browse files
committed
refactor: cursor pagination uses integer ids
1 parent 2f0ad82 commit 10db42b

2 files changed

Lines changed: 33 additions & 6 deletions

File tree

src/shared/utils/cursor.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,24 @@
22
import json
33
from datetime import datetime
44
from enum import Enum
5-
from uuid import UUID
65

76

87
class CursorDirection(Enum):
98
DIRECTION_NEXT = "next"
109
DIRECTION_PREV = "prev"
1110

1211

13-
def encode_cursor(created_at: datetime, id: UUID, dir: CursorDirection) -> str:
12+
def encode_cursor(created_at: datetime, id: int, dir: CursorDirection) -> str:
1413
"""
1514
Encode a cursor from timestamp and ID.
16-
Format: base64(json({"t": "ISO_TIMESTAMP", "id": "UUID"}))
15+
Format: base64(json({"t": "ISO_TIMESTAMP", "id": "INT"}))
1716
"""
18-
cursor_data = {"t": created_at.isoformat(), "id": str(id), "dir": dir.value}
17+
cursor_data = {"t": created_at.isoformat(), "id": id, "dir": dir.value}
1918
json_str = json.dumps(cursor_data)
2019
return base64.urlsafe_b64encode(json_str.encode()).decode()
2120

2221

23-
def decode_cursor(cursor: str) -> tuple[datetime, UUID, CursorDirection]:
22+
def decode_cursor(cursor: str) -> tuple[datetime, int, CursorDirection]:
2423
"""
2524
Decode a cursor back to timestamp and ID.
2625
Returns: (created_at, id)
@@ -30,7 +29,7 @@ def decode_cursor(cursor: str) -> tuple[datetime, UUID, CursorDirection]:
3029
cursor_data = json.loads(json_str)
3130
created_at = datetime.fromisoformat(cursor_data["t"])
3231
dir = CursorDirection(cursor_data["dir"])
33-
id = UUID(cursor_data["id"])
32+
id = int(cursor_data["id"])
3433
return created_at, id, dir
3534
except Exception as e:
3635
raise ValueError(f"Invalid cursor format: {e}")

tests/test_cursor.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from datetime import datetime
2+
3+
import pytest
4+
5+
from src.shared.utils.cursor import (
6+
CursorDirection,
7+
decode_cursor,
8+
encode_cursor,
9+
)
10+
11+
12+
def test_encode_decode_cursor_roundtrip_with_int_id():
13+
created_at = datetime(2026, 1, 1, 12, 30, 0)
14+
cursor = encode_cursor(created_at, 42, CursorDirection.DIRECTION_NEXT)
15+
decoded_created_at, decoded_id, decoded_dir = decode_cursor(cursor)
16+
assert decoded_created_at == created_at
17+
assert decoded_id == 42
18+
assert decoded_dir == CursorDirection.DIRECTION_NEXT
19+
20+
21+
def test_decode_cursor_rejects_invalid_id():
22+
cursor = encode_cursor(datetime.now(), 7, CursorDirection.DIRECTION_PREV)
23+
assert decode_cursor(cursor)[1] == 7
24+
25+
26+
def test_decode_cursor_rejects_garbage():
27+
with pytest.raises(ValueError, match="Invalid cursor format"):
28+
decode_cursor("not-a-cursor")

0 commit comments

Comments
 (0)