Archaeo commited on
Commit
9bb9cb8
·
verified ·
1 Parent(s): b12fc58

Update app/cache.py

Browse files
Files changed (1) hide show
  1. app/cache.py +37 -72
app/cache.py CHANGED
@@ -1,72 +1,37 @@
1
- from __future__ import annotations
2
-
3
- import asyncio
4
- import json
5
- import logging
6
- from collections import deque
7
- from pathlib import Path
8
- from typing import Deque
9
-
10
- from .models import Measurement
11
-
12
- logger = logging.getLogger(__name__)
13
-
14
-
15
- class MeasurementCache:
16
- def __init__(self, *, maxlen: int = 48, storage_path: Path | str = "data/cache.json") -> None:
17
- self._buffer: Deque[Measurement] = deque(maxlen=maxlen)
18
- self._lock = asyncio.Lock()
19
- self._path = Path(storage_path)
20
- self._path.parent.mkdir(parents=True, exist_ok=True)
21
- self._load_from_disk()
22
-
23
- def _load_from_disk(self) -> None:
24
- if not self._path.exists():
25
- return
26
- try:
27
- raw = json.loads(self._path.read_text("utf-8"))
28
- except json.JSONDecodeError as exc:
29
- logger.warning("Failed to read cache file %s: %s", self._path, exc)
30
- return
31
-
32
- if not isinstance(raw, list):
33
- logger.warning("Invalid cache file format: expected list, got %s", type(raw))
34
- return
35
-
36
- for item in raw[-self._buffer.maxlen :]:
37
- try:
38
- measurement = Measurement.model_validate(item)
39
- except Exception as exc: # noqa: BLE001
40
- logger.debug("Skipping invalid cache entry %s: %s", item, exc)
41
- continue
42
- self._buffer.append(measurement)
43
-
44
- logger.info("Loaded %s cached measurements", len(self._buffer))
45
-
46
- def _dump_to_disk(self) -> None:
47
- data = [m.model_dump(mode="json") for m in self._buffer]
48
- tmp_path = self._path.with_suffix(".tmp")
49
- tmp_path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
50
- tmp_path.replace(self._path)
51
-
52
- async def add(self, measurement: Measurement, *, persist: bool = True) -> None:
53
- async with self._lock:
54
- latest = self._buffer[-1] if self._buffer else None
55
- if latest and latest.timestamp == measurement.timestamp:
56
- self._buffer.pop()
57
- self._buffer.append(measurement)
58
-
59
- if persist:
60
- await asyncio.to_thread(self._dump_to_disk)
61
-
62
- async def get_latest(self) -> Measurement | None:
63
- async with self._lock:
64
- return self._buffer[-1] if self._buffer else None
65
-
66
- async def get_history(self) -> list[Measurement]:
67
- async with self._lock:
68
- return list(self._buffer)
69
-
70
- def __len__(self) -> int:
71
- return len(self._buffer)
72
-
 
1
+ # app/app/cache.py
2
+ from pathlib import Path
3
+ import os, json, tempfile, os
4
+ import asyncio
5
+
6
+ class Cache:
7
+ def __init__(self, filename: str = "cache.json"):
8
+ # bevorzugt /data (persistenter Storage auf HF), sonst /tmp
9
+ base = Path(os.getenv("APP_CACHE_DIR", "/data"))
10
+ if not os.access(base, os.W_OK):
11
+ base = Path("/tmp/app-cache")
12
+ base.mkdir(parents=True, exist_ok=True)
13
+
14
+ self.path = base / filename
15
+
16
+ def _dump_to_disk(self, data: dict | list | None = None):
17
+ # vorhandenen Zustand laden/ersetzen
18
+ if data is None and self.path.exists():
19
+ try:
20
+ data = json.loads(self.path.read_text(encoding="utf-8"))
21
+ except Exception:
22
+ data = None
23
+ data = data if data is not None else {}
24
+
25
+ # atomar schreiben
26
+ with tempfile.NamedTemporaryFile(
27
+ "w", delete=False, dir=self.path.parent, suffix=".tmp", encoding="utf-8"
28
+ ) as tmp:
29
+ json.dump(data, tmp, ensure_ascii=False, indent=2)
30
+ tmp_name = tmp.name
31
+ os.replace(tmp_name, self.path) # atomar
32
+
33
+ async def add(self, measurement: dict):
34
+ # ... füge measurement in deinen Speicher ein ...
35
+ # Beispiel (anpassen, falls du eine Liste etc. führst):
36
+ data = {"last": measurement}
37
+ await asyncio.to_thread(self._dump_to_disk, data)