File size: 2,839 Bytes
0815e01 b6f5bc4 0815e01 b6f5bc4 0815e01 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | """A store that remembers slow-changing reads (classes, members, roles, tests, assignments) for a short time.
Any change made through it to one of those forgets everything remembered at once. Rows come back as copies.
"""
from __future__ import annotations
import copy
import threading
import time
READS = frozenset({
"classes", "classes_of_student", "classes_taught", "members", "get_class", "roles_of",
"tests_of_classes", "get_test", "assignments_of_classes", "get_assignment", "all_students", "accounts", "curriculum",
"subjects",
})
CHANGES = frozenset({
"create_class", "update_class", "delete_class", "add_member", "remove_member", "give_role", "take_role", "link_user",
"create_test", "update_test", "remove_test", "create_assignment", "remove_assignment", "set_lesson_dates",
"create_subject", "update_subject", "delete_subject", "create_lesson", "save_lesson", "delete_lesson",
})
def _key(name: str, args: tuple, kwargs: dict):
def freeze(v):
if isinstance(v, (set, frozenset)):
return tuple(sorted(v, key=repr))
if isinstance(v, (list, tuple)):
return tuple(freeze(x) for x in v)
return v
return name, freeze(args), tuple(sorted((k, freeze(v)) for k, v in kwargs.items()))
class CachedStore:
def __init__(self, store, seconds: float, clock=time.monotonic) -> None:
self._store, self._seconds, self._clock = store, seconds, clock
self._lock = threading.Lock()
self._kept: dict = {} # key -> (expires, value)
self._generation = 0 # bumped by every change, so a read that began before one is not kept
def forget(self) -> None:
with self._lock:
self._kept.clear()
self._generation += 1
def __getattr__(self, name: str):
target = getattr(self._store, name)
if self._seconds <= 0 or not callable(target):
return target
if name in CHANGES:
def change(*args, **kwargs):
try:
return target(*args, **kwargs)
finally:
self.forget()
return change
if name in READS:
def read(*args, **kwargs):
key = _key(name, args, kwargs)
now = self._clock()
with self._lock:
hit = self._kept.get(key)
if hit and hit[0] > now:
return copy.deepcopy(hit[1])
generation = self._generation
value = target(*args, **kwargs)
with self._lock:
if generation == self._generation:
self._kept[key] = (now + self._seconds, copy.deepcopy(value))
return value
return read
return target
|