Files
simple-nanoshare/my_modules/filecache/store.py
T
daniel156161 73fdeacbfe
Build and Push Docker Container / build-and-push (push) Successful in 2m25s
feat!: adopt Python 3.14 and 3.15 language features
- Require Python 3.15 for the server and Python 3.14 for the CLI.
- Match the server requirement to quart_common's Python 3.15 features.
- Replace future annotations with native deferred evaluation.
- Defer CLI command dependencies and QR imports on Python 3.15.
- Enable argparse suggestions for mistyped CLI commands.
- Remove the obsolete tomllib import fallback.
- Update my_helpers and quart_common submodule references.
- Lock test dependencies and exclude them from Docker installations.
- Add regression coverage for lazy imports and deferred annotations.
- Document runtime requirements and standalone CLI validation.
- Validate 276 tests on Python 3.15 and 18 CLI tests on Python 3.14.

BREAKING CHANGE: The server now requires Python 3.15+ and the standalone CLI requires Python 3.14+.
2026-10-26 14:47:29 +01:00

61 lines
2.1 KiB
Python

import os
from collections.abc import Awaitable, Callable
from .blobs import BlobCache
from .metadata import FileMetadataCache
def _env_int(name: str, default: int) -> int:
try:
return int(os.getenv(name, str(default)))
except (TypeError, ValueError):
return default
class FileCache:
"""Facade over the metadata cache (shared) and the blob cache (process-local)."""
def __init__(self, metadata: FileMetadataCache, blobs: BlobCache, blob_max_age: int, edge_max_age: int):
self.metadata = metadata
self.blobs = blobs
self.blob_max_age = blob_max_age
self.edge_max_age = edge_max_age
async def get_metadata(self, file_id: str, loader: Callable[[], Awaitable[dict | None]]):
return await self.metadata.get(file_id, loader)
async def get_blob(self, storage_key: str, loader: Callable[[], Awaitable[bytes]]) -> bytes:
cached = self.blobs.get(storage_key)
if cached is not None:
return cached
data = await loader()
self.blobs.set(storage_key, data)
return data
async def invalidate(self, file_id: str, storage_key: str | None = None) -> None:
await self.metadata.invalidate(file_id)
if storage_key:
self.blobs.invalidate(storage_key)
def stats(self) -> dict:
return {
'metadata_hits': self.metadata.hits,
'metadata_misses': self.metadata.misses,
'blob_hits': self.blobs.hits,
'blob_misses': self.blobs.misses,
'blob_bytes': self.blobs.current_bytes,
}
def build_file_cache(cache) -> FileCache:
"""Build the cache stack from FILE_CACHE_* environment settings."""
return FileCache(
metadata=FileMetadataCache(cache, ttl=_env_int('FILE_CACHE_METADATA_TTL', 60)),
blobs=BlobCache(
max_total_bytes=_env_int('FILE_CACHE_BLOB_TOTAL_BYTES', 64 * 1024 * 1024),
max_item_bytes=_env_int('FILE_CACHE_BLOB_ITEM_BYTES', 2 * 1024 * 1024),
ttl=_env_int('FILE_CACHE_BLOB_TTL', 300),
),
blob_max_age=_env_int('FILE_CACHE_BROWSER_MAX_AGE', 86400),
# Cloudflare keeps serving deleted files, so the edge window stays short.
edge_max_age=_env_int('FILE_CACHE_EDGE_MAX_AGE', 1800),
)