-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathcaching.py
More file actions
154 lines (128 loc) · 5.67 KB
/
caching.py
File metadata and controls
154 lines (128 loc) · 5.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
"""
caching.py
Various caching routines for file content. Provides efficient caching mechanisms
for file content with automatic invalidation.
"""
# =============================================================================
# STANDARD LIBRARY IMPORTS
# =============================================================================
import os
import time
# =============================================================================
# LOCAL IMPORTS
# =============================================================================
from shared import g_logger
# =============================================================================
# GLOBAL VARIABLES AND CONSTANTS
# =============================================================================
_file_cache = {}
_FILE_CHECK_INTERVAL_SECONDS = 5 * 60 # 5 minutes
# =============================================================================
# FILE CACHING FUNCTIONS
# =============================================================================
def get_cached_file_content(file_path, encoding='utf-8'):
"""
Return content of any file, caching and invalidating when it changes.
Checks mtime only if _FILE_CHECK_INTERVAL_SECONDS have passed since the last check.
This provides a balance between performance and freshness.
Args:
file_path (str): Path to the file to read and cache
encoding (str): File encoding to use when reading (default: 'utf-8')
Returns:
str: File content, or empty string if file doesn't exist or is inaccessible
"""
now = time.monotonic()
entry = _file_cache.get(file_path)
# Check if cache entry exists and if we should skip the mtime check
if entry and (now - entry.get('last_check_time', 0)) < _FILE_CHECK_INTERVAL_SECONDS:
g_logger.debug(f"File cache hit (skipping mtime check): {file_path}")
return entry['content']
# Proceed with mtime check or initial load
try:
mtime = os.path.getmtime(file_path)
except OSError:
# File doesn't exist or inaccessible
if entry: # Remove stale entry if it exists
del _file_cache[file_path]
g_logger.info(f"Removed stale cache entry for non-existent file: {file_path}")
g_logger.warning(f"File not accessible: {file_path}")
return ''
# If cache entry exists and mtime matches, update check time and return content
if entry and entry['mtime'] == mtime:
entry['last_check_time'] = now
g_logger.debug(f"File cache hit (mtime unchanged): {file_path}")
return entry['content']
# Read file fresh or because mtime changed
try:
with open(file_path, 'r', encoding=encoding) as f:
content = f.read()
if entry:
g_logger.info(f"File cache miss (mtime changed), reloaded: {file_path}")
else:
g_logger.info(f"File cache miss (first load): {file_path}")
except FileNotFoundError:
content = ''
mtime = -1 # Or some other indicator that it's gone
g_logger.warning(f"File not found, caching empty content: {file_path}")
_file_cache[file_path] = {'mtime': mtime, 'content': content, 'last_check_time': now}
return content
_page_cache = {}
def get_cached_page(page_name, render_function, file_path=None):
"""
Cache a rendered page, only updating if the underlying file changes.
Only check the file's mtime every _FILE_CHECK_INTERVAL_SECONDS to avoid excess stat ops.
Args:
page_name (str): Unique cache name
render_function (callable): Function that returns the rendered HTML
file_path (str, optional): Path to a file to track for changes
Returns:
str: Cached HTML or newly rendered if file changed
"""
now = time.time()
cache_entry = _page_cache.get(page_name)
if cache_entry:
last_check = cache_entry.get('last_checked', 0)
# Only check mtime every _FILE_CHECK_INTERVAL_SECONDS
if now - last_check < _FILE_CHECK_INTERVAL_SECONDS:
# Don't check mtime until interval has elapsed
g_logger.debug(f"Page cache hit (skipping mtime check): {page_name}")
return cache_entry['html']
# Time to check if file changed
if file_path:
try:
mtime = os.path.getmtime(file_path)
except OSError:
mtime = None
else:
mtime = None
cached_mtime = cache_entry.get('file_mtime')
if mtime == cached_mtime:
# File unchanged, only update 'last_checked'
cache_entry['last_checked'] = now
g_logger.debug(f"Page cache hit (file unchanged): {page_name}")
return cache_entry['html']
# File changed! Will fall through and re-render.
g_logger.info(f"Page cache miss (file changed), re-rendering: {page_name}")
else:
# No cache, get mtime for initial cache
if file_path:
try:
mtime = os.path.getmtime(file_path)
except OSError:
mtime = None
else:
mtime = None
# No cache, or file changed, or missing
g_logger.info(f"Page cache miss (first render): {page_name}")
html = render_function()
_page_cache[page_name] = {
'html': html,
'file_mtime': mtime,
'last_checked': now
}
return html
# =============================================================================
# CACHE MANAGEMENT FUNCTIONS
# =============================================================================
# Note: Compression caching has been moved to routes.py as a simple cache key modifier
# No additional cache management functions are needed for the simplified approach