Spaces:
Sleeping
Sleeping
feat(cache): add in-memory caching for repo and community activity fetches
Browse files
app.py
CHANGED
|
@@ -41,6 +41,34 @@ rate_limiter = RateLimiter(calls_per_second=10)
|
|
| 41 |
recent_activity_rate_limiter = RateLimiter(calls_per_second=1.0)
|
| 42 |
|
| 43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
# ============================================================
|
| 45 |
# Public cache only
|
| 46 |
# ============================================================
|
|
@@ -502,10 +530,16 @@ def fetch_all_community_activity(username: str, selected_year: int):
|
|
| 502 |
username = (username or "").strip()
|
| 503 |
selected_year = int(selected_year)
|
| 504 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 505 |
if not username:
|
| 506 |
state = empty_community_state(selected_year)
|
| 507 |
state["fetched"] = True
|
| 508 |
state["logs"] = "No username provided."
|
|
|
|
| 509 |
return state
|
| 510 |
|
| 511 |
pr_opened = fetch_user_discussion_activity(username, selected_year, "pr_opened")
|
|
@@ -522,7 +556,7 @@ def fetch_all_community_activity(username: str, selected_year: int):
|
|
| 522 |
]
|
| 523 |
)
|
| 524 |
|
| 525 |
-
|
| 526 |
"username": username,
|
| 527 |
"selected_year": selected_year,
|
| 528 |
"fetched": True,
|
|
@@ -533,6 +567,9 @@ def fetch_all_community_activity(username: str, selected_year: int):
|
|
| 533 |
"logs": combined_logs,
|
| 534 |
}
|
| 535 |
|
|
|
|
|
|
|
|
|
|
| 536 |
|
| 537 |
# ============================================================
|
| 538 |
# Contribution grid helpers
|
|
@@ -867,6 +904,12 @@ def build_app_state(
|
|
| 867 |
"auth_text": "No username provided.",
|
| 868 |
}
|
| 869 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 870 |
if profile is not None and is_own_profile:
|
| 871 |
auth_text = (
|
| 872 |
f'Logged in as <b>{html.escape(viewer_username)}</b>. '
|
|
@@ -889,7 +932,7 @@ def build_app_state(
|
|
| 889 |
token=token_for_fetch,
|
| 890 |
)
|
| 891 |
|
| 892 |
-
|
| 893 |
"username": username,
|
| 894 |
"selected_year": selected_year,
|
| 895 |
"viewer_username": viewer_username,
|
|
@@ -900,6 +943,9 @@ def build_app_state(
|
|
| 900 |
"auth_text": auth_text,
|
| 901 |
}
|
| 902 |
|
|
|
|
|
|
|
|
|
|
| 903 |
|
| 904 |
def render_app_state(app_state: dict | None, visibility_mode: str | None):
|
| 905 |
if not app_state or not app_state.get("username"):
|
|
|
|
| 41 |
recent_activity_rate_limiter = RateLimiter(calls_per_second=1.0)
|
| 42 |
|
| 43 |
|
| 44 |
+
# ============================================================
|
| 45 |
+
# Simple in-memory cache
|
| 46 |
+
# ============================================================
|
| 47 |
+
CACHE = {}
|
| 48 |
+
CACHE_TTL = 60 * 10 # 10 minutes
|
| 49 |
+
CACHE_LOCK = threading.Lock()
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def get_cache(key):
|
| 53 |
+
with CACHE_LOCK:
|
| 54 |
+
entry = CACHE.get(key)
|
| 55 |
+
if not entry:
|
| 56 |
+
return None
|
| 57 |
+
|
| 58 |
+
value, timestamp = entry
|
| 59 |
+
if time.time() - timestamp > CACHE_TTL:
|
| 60 |
+
del CACHE[key]
|
| 61 |
+
return None
|
| 62 |
+
|
| 63 |
+
print(f"[CACHE HIT] {key}")
|
| 64 |
+
return value
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def set_cache(key, value):
|
| 68 |
+
with CACHE_LOCK:
|
| 69 |
+
CACHE[key] = (value, time.time())
|
| 70 |
+
|
| 71 |
+
|
| 72 |
# ============================================================
|
| 73 |
# Public cache only
|
| 74 |
# ============================================================
|
|
|
|
| 530 |
username = (username or "").strip()
|
| 531 |
selected_year = int(selected_year)
|
| 532 |
|
| 533 |
+
cache_key = (username.lower(), selected_year, "community")
|
| 534 |
+
cached = get_cache(cache_key)
|
| 535 |
+
if cached:
|
| 536 |
+
return cached
|
| 537 |
+
|
| 538 |
if not username:
|
| 539 |
state = empty_community_state(selected_year)
|
| 540 |
state["fetched"] = True
|
| 541 |
state["logs"] = "No username provided."
|
| 542 |
+
set_cache(cache_key, state)
|
| 543 |
return state
|
| 544 |
|
| 545 |
pr_opened = fetch_user_discussion_activity(username, selected_year, "pr_opened")
|
|
|
|
| 556 |
]
|
| 557 |
)
|
| 558 |
|
| 559 |
+
result = {
|
| 560 |
"username": username,
|
| 561 |
"selected_year": selected_year,
|
| 562 |
"fetched": True,
|
|
|
|
| 567 |
"logs": combined_logs,
|
| 568 |
}
|
| 569 |
|
| 570 |
+
set_cache(cache_key, result)
|
| 571 |
+
return result
|
| 572 |
+
|
| 573 |
|
| 574 |
# ============================================================
|
| 575 |
# Contribution grid helpers
|
|
|
|
| 904 |
"auth_text": "No username provided.",
|
| 905 |
}
|
| 906 |
|
| 907 |
+
scope = "repo_private" if is_own_profile else "repo_public"
|
| 908 |
+
cache_key = (username.lower(), selected_year, scope)
|
| 909 |
+
cached = get_cache(cache_key)
|
| 910 |
+
if cached:
|
| 911 |
+
return cached
|
| 912 |
+
|
| 913 |
if profile is not None and is_own_profile:
|
| 914 |
auth_text = (
|
| 915 |
f'Logged in as <b>{html.escape(viewer_username)}</b>. '
|
|
|
|
| 932 |
token=token_for_fetch,
|
| 933 |
)
|
| 934 |
|
| 935 |
+
result = {
|
| 936 |
"username": username,
|
| 937 |
"selected_year": selected_year,
|
| 938 |
"viewer_username": viewer_username,
|
|
|
|
| 943 |
"auth_text": auth_text,
|
| 944 |
}
|
| 945 |
|
| 946 |
+
set_cache(cache_key, result)
|
| 947 |
+
return result
|
| 948 |
+
|
| 949 |
|
| 950 |
def render_app_state(app_state: dict | None, visibility_mode: str | None):
|
| 951 |
if not app_state or not app_state.get("username"):
|