Skip to content

Commit a07a54b

Browse files
bajnokkc00kiemon5ter
authored andcommitted
storage: add automatic expiry to MongoDB
This changes the record layout in MongoDB so that the modification time is now stored as a datetime object instead of a timestamp. It enables us to create an auto-expiry index on that field. Note that expiring objects is an asynchronous task in MongoDB. Unlike Redis, MongoDB uses collection-level expiry settings, therefore it is not possible to create multiple wrapper objects that operate on the same collection with different TTL values. Additionally, there is a minor change that allows specifying the database name in the URI instead of an argument. In case the name is specified both in the URI and the argument, the one in the argument is silently ignored.
1 parent 578c769 commit a07a54b

File tree

2 files changed

+32
-7
lines changed

2 files changed

+32
-7
lines changed

src/pyop/storage.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import json
66
import pymongo
77
from redis.client import Redis
8-
from time import time
8+
from datetime import datetime
99

1010

1111
class StorageBase(ABC):
@@ -54,18 +54,29 @@ def ttl(self):
5454

5555

5656
class MongoWrapper(StorageBase):
57-
def __init__(self, db_uri, db_name, collection):
57+
def __init__(self, db_uri, db_name, collection, ttl=None):
5858
self._db_uri = db_uri
5959
self._coll_name = collection
6060
self._db = MongoDB(db_uri, db_name=db_name)
6161
self._coll = self._db.get_collection(collection)
6262
self._coll.create_index('lookup_key', unique=True)
6363

64+
if ttl is None or (isinstance(ttl, int) and ttl >= 0):
65+
self._ttl = ttl
66+
else:
67+
raise ValueError("TTL must be a non-negative integer or None")
68+
if ttl is not None:
69+
self._coll.create_index(
70+
'last_modified',
71+
expireAfterSeconds=ttl,
72+
name="expiry"
73+
)
74+
6475
def __setitem__(self, key, value):
6576
doc = {
6677
'lookup_key': key,
6778
'data': value,
68-
'modified_ts': time()
79+
'last_modified': datetime.utcnow()
6980
}
7081
self._coll.replace_one({'lookup_key': key}, doc, upsert=True)
7182

@@ -143,14 +154,16 @@ def __init__(self, db_uri, db_name=None,
143154
if db_uri is None:
144155
raise ValueError('db_uri not supplied')
145156

146-
self._db_uri = db_uri
147-
self._database_name = db_name
148157
self._sanitized_uri = None
149158

150159
self._parsed_uri = pymongo.uri_parser.parse_uri(db_uri)
151160

152-
if self._parsed_uri.get('database') is None:
153-
self._parsed_uri['database'] = db_name
161+
db_name = self._parsed_uri.get('database') or db_name
162+
if db_name is None:
163+
raise ValueError(
164+
"Database name must be provided either in the URI or as an argument"
165+
)
166+
self._database_name = self._parsed_uri['database'] = db_name
154167

155168
if 'replicaSet' in kwargs and kwargs['replicaSet'] is None:
156169
del kwargs['replicaSet']

tests/pyop/test_storage.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,15 @@ def new_time():
168168

169169
def test_ttl(self):
170170
self.execute_ttl_test("redis://localhost/0", 3600)
171+
172+
173+
class TestMongoTTL(StorageTTLTest):
174+
def set_time(self, offset, monkeypatch):
175+
now = datetime.datetime.utcnow()
176+
def new_time():
177+
return now + datetime.timedelta(seconds=offset)
178+
179+
monkeypatch.setattr(mongomock, "utcnow", new_time)
180+
181+
def test_ttl(self):
182+
self.execute_ttl_test("mongodb://localhost/pyop", 3600)

0 commit comments

Comments
 (0)