Download apps/core/infrastructure/cache.py from jun2024/fc: direct link, hf CLI and curl.
- Browser
- Download file 10.5 kB
-
https://huggingface.co/spaces/jun2024/fc/resolve/main/apps/core/infrastructure/cache.py
- Command line
-
hf download hf://spaces/jun2024/fc/apps/core/infrastructure/cache.py
-
curl -L -o cache.py https://huggingface.co/spaces/jun2024/fc/resolve/main/apps/core/infrastructure/cache.py
10.5 kB
| """Module for cache.""" | |
| from __future__ import annotations | |
| """ | |
| 缓存配置模块 | |
| 提供 Django 内置本地内存缓存配置 | |
| """ | |
| import os | |
| from typing import Any | |
| def _safe_get_config(key: str, default: Any | None = None) -> Any: | |
| """安全获取配置,避免循环导入""" | |
| try: | |
| from apps.core.config import get_config | |
| return get_config(key, default) | |
| except (ImportError, AttributeError, KeyError): | |
| return default | |
| def _hash_key_component(value: str) -> str: | |
| import hashlib | |
| import hmac | |
| from django.conf import settings | |
| v = (value or "").strip().encode("utf-8") | |
| secret = (getattr(settings, "SECRET_KEY", "") or "").encode("utf-8") | |
| return hmac.new(secret, v, hashlib.sha256).hexdigest()[:32] | |
| def _normalize_key_component(value: str, *, max_len: int = 64) -> str: | |
| import re | |
| raw = (value or "").strip() | |
| if not raw: | |
| return "empty" | |
| normalized = raw.lower() | |
| if re.fullmatch(r"[a-z0-9._-]+", normalized) and len(normalized) <= max_len: | |
| return normalized | |
| cleaned = re.sub(r"[^a-z0-9._-]+", "-", normalized).strip("-._") | |
| if not cleaned: | |
| cleaned = "x" | |
| if len(cleaned) > max_len: | |
| cleaned = cleaned[:max_len].rstrip("-._") or "x" | |
| return f"{cleaned}-{_hash_key_component(raw)}" | |
| def get_cache_config() -> dict[str, Any]: | |
| """ | |
| 获取缓存配置 | |
| 使用 Django 内置的本地内存缓存,无需外部依赖. | |
| Returns: | |
| Django CACHES 配置字典 | |
| """ | |
| default_timeout = _safe_get_config("performance.cache.default_timeout", 300) | |
| redis_url = (os.environ.get("DJANGO_CACHE_REDIS_URL", "") or "").strip() | |
| if redis_url: | |
| return { | |
| "default": { | |
| "BACKEND": "django.core.cache.backends.redis.RedisCache", | |
| "LOCATION": redis_url, | |
| "KEY_PREFIX": _safe_get_config("performance.cache.key_prefix", "lawfirm"), | |
| "TIMEOUT": default_timeout, | |
| } | |
| } | |
| return { | |
| "default": { | |
| "BACKEND": "django.core.cache.backends.locmem.LocMemCache", | |
| "LOCATION": "unique-lawfirm-cache", | |
| "OPTIONS": { | |
| "MAX_ENTRIES": 1000, | |
| }, | |
| "KEY_PREFIX": _safe_get_config("performance.cache.key_prefix", "lawfirm"), | |
| "TIMEOUT": default_timeout, | |
| } | |
| } | |
| # 缓存 key 常量 | |
| class CacheKeys: | |
| """缓存 key 定义""" | |
| # 用户相关 | |
| USER_ORG_ACCESS = "user:org_access:{user_id}" # 用户组织访问权限 | |
| USER_TEAMS = "user:teams:{user_id}" # 用户团队列表 | |
| # 案件相关 | |
| CASE_ACCESS_GRANTS = "case:access_grants:{user_id}" # 用户案件访问授权 | |
| # 自动化相关 | |
| AUTOMATION_COURT_SMS_RECOVERY_SCHEDULED = "automation:court_sms_recovery_scheduled" | |
| # 配置相关 | |
| CASE_STAGES_CONFIG = "config:case_stages" # 案件阶段配置 | |
| LEGAL_STATUS_CONFIG = "config:legal_status" # 诉讼地位配置 | |
| SYSTEM_CONFIG = "system_config:{key}" | |
| # 法院系统 Token 相关 | |
| COURT_TOKEN = "court_token:{site_name}:{account}" # 法院系统 Token | |
| # 自动化 Token 性能监控 | |
| AUTOMATION_TOKEN_PERF_ACQUISITION = "automation:token:perf:acquisition:{acquisition_id}" | |
| AUTOMATION_TOKEN_PERF_CONCURRENT = "automation:token:perf:concurrent:{site_name}" | |
| AUTOMATION_TOKEN_PERF_COUNTER = "automation:token:perf:counter:{date}:{site_name}:{metric}" | |
| # Prompt 相关 | |
| PROMPT_TEMPLATE = "prompt_template:{name}" | |
| PROMPT_VERSION_ACTIVE = "prompt_version:active:{name}" | |
| # Documents 模板匹配相关(包含版本号以便模板变更自动失效) | |
| DOCUMENTS_MATCH_CONTRACT_TEMPLATES = "documents:matching:contract_templates:{case_type}:{version}" | |
| DOCUMENTS_MATCH_FOLDER_TEMPLATES = "documents:matching:folder_templates:{template_type}:{case_type}:{version}" | |
| DOCUMENTS_MATCH_CASE_FILE_TEMPLATES = ( | |
| "documents:matching:case_file_templates:{case_type}:{case_stage}:{institutions}:{version}" | |
| ) | |
| DOCUMENTS_MATCHING_VERSION_DOCUMENT_TEMPLATES = "documents:matching:version:document_templates" | |
| DOCUMENTS_MATCHING_VERSION_FOLDER_TEMPLATES = "documents:matching:version:folder_templates" | |
| def user_org_access(cls, user_id: int) -> str: | |
| return cls.USER_ORG_ACCESS.format(user_id=user_id) | |
| def user_teams(cls, user_id: int) -> str: | |
| return cls.USER_TEAMS.format(user_id=user_id) | |
| def case_access_grants(cls, user_id: int) -> str: | |
| return cls.CASE_ACCESS_GRANTS.format(user_id=user_id) | |
| def automation_court_sms_recovery_scheduled(cls) -> str: | |
| return cls.AUTOMATION_COURT_SMS_RECOVERY_SCHEDULED | |
| def court_token(cls, site_name: str, account: str) -> str: | |
| return cls.COURT_TOKEN.format( | |
| site_name=_normalize_key_component(site_name, max_len=64), | |
| account=_hash_key_component(account), | |
| ) | |
| def automation_token_perf_acquisition(cls, acquisition_id: str) -> str: | |
| return cls.AUTOMATION_TOKEN_PERF_ACQUISITION.format(acquisition_id=acquisition_id) | |
| def automation_token_perf_concurrent(cls, *, site_name: str = "all") -> str: | |
| return cls.AUTOMATION_TOKEN_PERF_CONCURRENT.format(site_name=site_name or "all") | |
| def automation_token_perf_counter(cls, *, date: str, site_name: str, metric: str) -> str: | |
| return cls.AUTOMATION_TOKEN_PERF_COUNTER.format(date=date, site_name=site_name or "all", metric=metric) | |
| def prompt_template(cls, name: str) -> str: | |
| return cls.PROMPT_TEMPLATE.format(name=name) | |
| def prompt_version_active(cls, name: str) -> str: | |
| return cls.PROMPT_VERSION_ACTIVE.format(name=name) | |
| def system_config(cls, key: str) -> str: | |
| return cls.SYSTEM_CONFIG.format(key=key) | |
| def documents_matching_contract_templates(cls, *, case_type: str, version: int) -> str: | |
| return cls.DOCUMENTS_MATCH_CONTRACT_TEMPLATES.format(case_type=case_type or "", version=version or 1) | |
| def documents_matching_folder_templates(cls, *, template_type: str, case_type: str, version: int) -> str: | |
| return cls.DOCUMENTS_MATCH_FOLDER_TEMPLATES.format( | |
| template_type=template_type or "", | |
| case_type=case_type or "", | |
| version=version or 1, | |
| ) | |
| def documents_matching_case_file_templates( | |
| cls, *, case_type: str, case_stage: str, institutions: str = "", version: int | |
| ) -> str: | |
| return cls.DOCUMENTS_MATCH_CASE_FILE_TEMPLATES.format( | |
| case_type=case_type or "", | |
| case_stage=case_stage or "", | |
| institutions=institutions or "all", | |
| version=version or 1, | |
| ) | |
| def documents_matching_version_document_templates(cls) -> str: | |
| return cls.DOCUMENTS_MATCHING_VERSION_DOCUMENT_TEMPLATES | |
| def documents_matching_version_folder_templates(cls) -> str: | |
| return cls.DOCUMENTS_MATCHING_VERSION_FOLDER_TEMPLATES | |
| # 缓存超时时间(秒) | |
| class _CacheTimeoutMeta(type): | |
| def __getattribute__(cls, name: str) -> int: | |
| if name == "SHORT": | |
| return int(_safe_get_config("performance.cache.timeout_short", 60)) | |
| if name == "MEDIUM": | |
| return int(_safe_get_config("performance.cache.timeout_medium", 300)) | |
| if name == "LONG": | |
| return int(_safe_get_config("performance.cache.timeout_long", 3600)) | |
| if name == "DAY": | |
| return int(_safe_get_config("performance.cache.timeout_day", 86400)) | |
| return super().__getattribute__(name) # type: ignore[no-any-return] # metaclass __getattribute__ 返回 Any | |
| class CacheTimeout(metaclass=_CacheTimeoutMeta): | |
| """缓存超时时间定义""" | |
| def get_short(cls) -> int: | |
| """短期缓存(1分钟)""" | |
| return int(cls.SHORT) | |
| def get_medium(cls) -> int: | |
| """中期缓存(5分钟)""" | |
| return int(cls.MEDIUM) | |
| def get_long(cls) -> int: | |
| """长期缓存(1小时)""" | |
| return int(cls.LONG) | |
| def get_day(cls) -> int: | |
| """日缓存(1天)""" | |
| return int(cls.DAY) | |
| def until_end_of_day(cls, *, now: Any | None = None, buffer_seconds: int = 3600) -> int: | |
| from datetime import datetime, time, timedelta | |
| from django.utils import timezone | |
| if now is None: | |
| now = timezone.now() | |
| if timezone.is_naive(now): | |
| now = timezone.make_aware(now, timezone.get_current_timezone()) | |
| end = timezone.make_aware( | |
| datetime.combine(now.date() + timedelta(days=1), time.min), | |
| timezone.get_current_timezone(), | |
| ) | |
| seconds = int((end - now).total_seconds()) + int(buffer_seconds or 0) | |
| return max(1, seconds) | |
| # 保持向后兼容的常量 | |
| SHORT = 60 | |
| MEDIUM = 300 | |
| LONG = 3600 | |
| DAY = 86400 | |
| def invalidate_user_access_context(user_id: int, *, org_access: bool = True, case_grants: bool = True) -> None: | |
| from django.core.cache import cache | |
| keys: list[Any] = [] | |
| if org_access: | |
| keys.append(CacheKeys.user_org_access(user_id)) | |
| if case_grants: | |
| keys.append(CacheKeys.case_access_grants(user_id)) | |
| if keys: | |
| cache.delete_many(keys) | |
| def invalidate_users_access_context(user_ids: list[int], *, org_access: bool = True, case_grants: bool = True) -> None: | |
| from django.core.cache import cache | |
| keys: list[str] = [] | |
| for user_id in user_ids or []: | |
| if org_access: | |
| keys.append(CacheKeys.user_org_access(user_id)) | |
| if case_grants: | |
| keys.append(CacheKeys.case_access_grants(user_id)) | |
| if keys: | |
| cache.delete_many(keys) | |
| def bump_cache_version(key: str, *, timeout: int) -> int: | |
| from django.core.cache import cache | |
| cache.add(key, 1, timeout=timeout) | |
| try: | |
| return int(cache.incr(key)) | |
| except (ConnectionError, TimeoutError, OSError, ValueError, TypeError): | |
| current = int(cache.get(key) or 1) | |
| new_val = current + 1 | |
| cache.set(key, new_val, timeout=timeout) | |
| return new_val | |
| def delete_cache_key(key: str) -> None: | |
| from django.core.cache import cache | |
| cache.delete(key) | |
| import logging | |
| logger = logging.getLogger(__name__) | |