Download mathly2/cached.py from wzender/mathly: direct link, hf CLI and curl.
- Browser
- Download file 2.84 kB
-
https://huggingface.co/spaces/wzender/mathly/resolve/main/mathly2/cached.py
- Command line
-
hf download hf://spaces/wzender/mathly/mathly2/cached.py
-
curl -L -o cached.py https://huggingface.co/spaces/wzender/mathly/resolve/main/mathly2/cached.py
2.84 kB
| """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 | |