Spaces:
Runtime error
Runtime error
- Updated `.env.example` to include new settings for bulk upload limits and batch processing.
3c31a2a | """Per-tenant writing style profile cache. | |
| Style profiles are persisted as JSON files under ``settings.cache_dir / styles/`` | |
| so they survive server restarts. Each file is named ``<tenant_id>.json``. | |
| Cached profiles expire after ``_STYLE_CACHE_TTL_DAYS`` days so that newly | |
| uploaded documents can shift the detected style on the next generation call. | |
| """ | |
| import json | |
| import logging | |
| import time | |
| from pathlib import Path | |
| from app.config import settings | |
| from app.models.schemas import WritingStyleProfile | |
| logger = logging.getLogger(__name__) | |
| # Style profiles are re-analysed after this many days of inactivity. | |
| # Set to 0 to disable TTL (cache lives forever until explicitly invalidated). | |
| _STYLE_CACHE_TTL_DAYS: int = 7 | |
| _STYLE_CACHE_TTL_SECS: float = _STYLE_CACHE_TTL_DAYS * 86_400 | |
| def _style_dir() -> Path: | |
| d = Path(settings.cache_dir) / "styles" | |
| d.mkdir(parents=True, exist_ok=True) | |
| return d | |
| def _path(tenant_id: str) -> Path: | |
| safe = "".join(c if c.isalnum() or c in "-_" else "_" for c in tenant_id) | |
| return _style_dir() / f"{safe}.json" | |
| def get(tenant_id: str) -> WritingStyleProfile | None: | |
| """Return a cached :class:`WritingStyleProfile` for ``tenant_id``, or ``None``. | |
| Returns ``None`` (forcing a fresh analysis) when: | |
| * No cached file exists. | |
| * The cached file is older than ``_STYLE_CACHE_TTL_DAYS``. | |
| * The cached file is corrupt / unreadable. | |
| Args: | |
| tenant_id: The tenant whose style profile to look up. | |
| Returns: | |
| :class:`WritingStyleProfile` if cached and fresh, otherwise ``None``. | |
| """ | |
| p = _path(tenant_id) | |
| if not p.exists(): | |
| return None | |
| # TTL check — expire stale profiles so new uploads can shift the style. | |
| if _STYLE_CACHE_TTL_SECS > 0: | |
| age = time.time() - p.stat().st_mtime | |
| if age > _STYLE_CACHE_TTL_SECS: | |
| logger.debug( | |
| "Style cache expired (%.0f days old) for tenant=%s — will re-analyse", | |
| age / 86_400, | |
| tenant_id, | |
| ) | |
| p.unlink(missing_ok=True) | |
| return None | |
| try: | |
| data = json.loads(p.read_text(encoding="utf-8")) | |
| return WritingStyleProfile(**data) | |
| except Exception as exc: | |
| logger.warning("Could not read style cache for %s: %s", tenant_id, exc) | |
| return None | |
| def set(tenant_id: str, profile: WritingStyleProfile) -> None: | |
| """Persist ``profile`` for ``tenant_id`` atomically. | |
| Writes to a temp file then renames so a crash mid-write never corrupts | |
| the stored profile. | |
| Args: | |
| tenant_id: The tenant whose profile to store. | |
| profile: :class:`WritingStyleProfile` to serialise and save. | |
| """ | |
| p = _path(tenant_id) | |
| tmp = p.with_suffix(".tmp") | |
| try: | |
| tmp.write_text(profile.model_dump_json(indent=2), encoding="utf-8") | |
| tmp.replace(p) # atomic rename | |
| logger.debug("Style profile cached for tenant=%s", tenant_id) | |
| except Exception as exc: | |
| logger.warning("Could not write style cache for %s: %s", tenant_id, exc) | |
| tmp.unlink(missing_ok=True) | |
| def invalidate(tenant_id: str) -> None: | |
| """Remove the cached profile for ``tenant_id`` (force re-analysis on next call). | |
| Args: | |
| tenant_id: The tenant whose cache entry to delete. | |
| """ | |
| p = _path(tenant_id) | |
| if p.exists(): | |
| p.unlink(missing_ok=True) | |
| logger.debug("Style cache invalidated for tenant=%s", tenant_id) | |