|
1 | 1 | """Convert Claude Code session JSON to a clean mobile-friendly HTML page with pagination.""" |
2 | 2 |
|
| 3 | +import contextvars |
3 | 4 | import json |
4 | 5 | import html |
5 | 6 | import os |
@@ -234,9 +235,49 @@ def extract_text_from_content(content): |
234 | 235 | return "" |
235 | 236 |
|
236 | 237 |
|
237 | | -# Module-level variable for GitHub repo (set by generate_html) |
| 238 | +# Thread-safe context variable for GitHub repo (set by generate_html) |
| 239 | +# Using contextvars ensures thread-safety when processing multiple sessions concurrently |
| 240 | +_github_repo_var: contextvars.ContextVar[str | None] = contextvars.ContextVar( |
| 241 | + "_github_repo", default=None |
| 242 | +) |
| 243 | + |
| 244 | +# Backward compatibility: module-level variable that tests may still access |
| 245 | +# This is deprecated - use get_github_repo() and set_github_repo() instead |
238 | 246 | _github_repo = None |
239 | 247 |
|
| 248 | + |
| 249 | +def get_github_repo() -> str | None: |
| 250 | + """Get the current GitHub repo from the thread-local context. |
| 251 | +
|
| 252 | + This is the thread-safe way to access the GitHub repo setting. |
| 253 | + Falls back to the module-level _github_repo for backward compatibility. |
| 254 | +
|
| 255 | + Returns: |
| 256 | + The GitHub repository in 'owner/repo' format, or None if not set. |
| 257 | + """ |
| 258 | + ctx_value = _github_repo_var.get() |
| 259 | + if ctx_value is not None: |
| 260 | + return ctx_value |
| 261 | + # Fallback for backward compatibility |
| 262 | + return _github_repo |
| 263 | + |
| 264 | + |
| 265 | +def set_github_repo(repo: str | None) -> contextvars.Token[str | None]: |
| 266 | + """Set the GitHub repo in the thread-local context. |
| 267 | +
|
| 268 | + This is the thread-safe way to set the GitHub repo. Also updates |
| 269 | + the module-level _github_repo for backward compatibility. |
| 270 | +
|
| 271 | + Args: |
| 272 | + repo: The GitHub repository in 'owner/repo' format, or None. |
| 273 | +
|
| 274 | + Returns: |
| 275 | + A token that can be used to reset the value later. |
| 276 | + """ |
| 277 | + global _github_repo |
| 278 | + _github_repo = repo |
| 279 | + return _github_repo_var.set(repo) |
| 280 | + |
240 | 281 | # API constants |
241 | 282 | API_BASE_URL = "https://api.anthropic.com/v1" |
242 | 283 | ANTHROPIC_VERSION = "2023-06-01" |
@@ -1058,7 +1099,9 @@ def render_content_block(block): |
1058 | 1099 | commit_hash = match.group(1) |
1059 | 1100 | commit_msg = match.group(2) |
1060 | 1101 | parts.append( |
1061 | | - _macros.commit_card(commit_hash, commit_msg, _github_repo) |
| 1102 | + _macros.commit_card( |
| 1103 | + commit_hash, commit_msg, get_github_repo() |
| 1104 | + ) |
1062 | 1105 | ) |
1063 | 1106 | last_end = match.end() |
1064 | 1107 |
|
@@ -2148,9 +2191,8 @@ def generate_html(json_path, output_dir, github_repo=None): |
2148 | 2191 | "Warning: Could not auto-detect GitHub repo. Commit links will be disabled." |
2149 | 2192 | ) |
2150 | 2193 |
|
2151 | | - # Set module-level variable for render functions |
2152 | | - global _github_repo |
2153 | | - _github_repo = github_repo |
| 2194 | + # Set thread-safe context variable for render functions |
| 2195 | + set_github_repo(github_repo) |
2154 | 2196 |
|
2155 | 2197 | conversations = [] |
2156 | 2198 | current_conv = None |
@@ -2304,7 +2346,7 @@ def generate_html(json_path, output_dir, github_repo=None): |
2304 | 2346 | # Add commits as separate timeline items |
2305 | 2347 | for commit_ts, commit_hash, commit_msg, page_num, conv_idx in all_commits: |
2306 | 2348 | item_html = _macros.index_commit( |
2307 | | - commit_hash, commit_msg, commit_ts, _github_repo |
| 2349 | + commit_hash, commit_msg, commit_ts, get_github_repo() |
2308 | 2350 | ) |
2309 | 2351 | timeline_items.append((commit_ts, "commit", item_html)) |
2310 | 2352 |
|
@@ -2650,9 +2692,8 @@ def generate_html_from_session_data(session_data, output_dir, github_repo=None): |
2650 | 2692 | if github_repo: |
2651 | 2693 | click.echo(f"Auto-detected GitHub repo: {github_repo}") |
2652 | 2694 |
|
2653 | | - # Set module-level variable for render functions |
2654 | | - global _github_repo |
2655 | | - _github_repo = github_repo |
| 2695 | + # Set thread-safe context variable for render functions |
| 2696 | + set_github_repo(github_repo) |
2656 | 2697 |
|
2657 | 2698 | conversations = [] |
2658 | 2699 | current_conv = None |
@@ -2806,7 +2847,7 @@ def generate_html_from_session_data(session_data, output_dir, github_repo=None): |
2806 | 2847 | # Add commits as separate timeline items |
2807 | 2848 | for commit_ts, commit_hash, commit_msg, page_num, conv_idx in all_commits: |
2808 | 2849 | item_html = _macros.index_commit( |
2809 | | - commit_hash, commit_msg, commit_ts, _github_repo |
| 2850 | + commit_hash, commit_msg, commit_ts, get_github_repo() |
2810 | 2851 | ) |
2811 | 2852 | timeline_items.append((commit_ts, "commit", item_html)) |
2812 | 2853 |
|
|
0 commit comments