|
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 |
@@ -261,9 +262,49 @@ def extract_text_from_content(content): |
261 | 262 | return "" |
262 | 263 |
|
263 | 264 |
|
264 | | -# Module-level variable for GitHub repo (set by generate_html) |
| 265 | +# Thread-safe context variable for GitHub repo (set by generate_html) |
| 266 | +# Using contextvars ensures thread-safety when processing multiple sessions concurrently |
| 267 | +_github_repo_var: contextvars.ContextVar[str | None] = contextvars.ContextVar( |
| 268 | + "_github_repo", default=None |
| 269 | +) |
| 270 | + |
| 271 | +# Backward compatibility: module-level variable that tests may still access |
| 272 | +# This is deprecated - use get_github_repo() and set_github_repo() instead |
265 | 273 | _github_repo = None |
266 | 274 |
|
| 275 | + |
| 276 | +def get_github_repo() -> str | None: |
| 277 | + """Get the current GitHub repo from the thread-local context. |
| 278 | +
|
| 279 | + This is the thread-safe way to access the GitHub repo setting. |
| 280 | + Falls back to the module-level _github_repo for backward compatibility. |
| 281 | +
|
| 282 | + Returns: |
| 283 | + The GitHub repository in 'owner/repo' format, or None if not set. |
| 284 | + """ |
| 285 | + ctx_value = _github_repo_var.get() |
| 286 | + if ctx_value is not None: |
| 287 | + return ctx_value |
| 288 | + # Fallback for backward compatibility |
| 289 | + return _github_repo |
| 290 | + |
| 291 | + |
| 292 | +def set_github_repo(repo: str | None) -> contextvars.Token[str | None]: |
| 293 | + """Set the GitHub repo in the thread-local context. |
| 294 | +
|
| 295 | + This is the thread-safe way to set the GitHub repo. Also updates |
| 296 | + the module-level _github_repo for backward compatibility. |
| 297 | +
|
| 298 | + Args: |
| 299 | + repo: The GitHub repository in 'owner/repo' format, or None. |
| 300 | +
|
| 301 | + Returns: |
| 302 | + A token that can be used to reset the value later. |
| 303 | + """ |
| 304 | + global _github_repo |
| 305 | + _github_repo = repo |
| 306 | + return _github_repo_var.set(repo) |
| 307 | + |
267 | 308 | # API constants |
268 | 309 | API_BASE_URL = "https://api.anthropic.com/v1" |
269 | 310 | ANTHROPIC_VERSION = "2023-06-01" |
@@ -1016,7 +1057,9 @@ def render_content_block(block): |
1016 | 1057 | commit_hash = match.group(1) |
1017 | 1058 | commit_msg = match.group(2) |
1018 | 1059 | parts.append( |
1019 | | - _macros.commit_card(commit_hash, commit_msg, _github_repo) |
| 1060 | + _macros.commit_card( |
| 1061 | + commit_hash, commit_msg, get_github_repo() |
| 1062 | + ) |
1020 | 1063 | ) |
1021 | 1064 | last_end = match.end() |
1022 | 1065 |
|
@@ -2035,9 +2078,8 @@ def generate_html(json_path, output_dir, github_repo=None): |
2035 | 2078 | "Warning: Could not auto-detect GitHub repo. Commit links will be disabled." |
2036 | 2079 | ) |
2037 | 2080 |
|
2038 | | - # Set module-level variable for render functions |
2039 | | - global _github_repo |
2040 | | - _github_repo = github_repo |
| 2081 | + # Set thread-safe context variable for render functions |
| 2082 | + set_github_repo(github_repo) |
2041 | 2083 |
|
2042 | 2084 | conversations = [] |
2043 | 2085 | current_conv = None |
@@ -2191,7 +2233,7 @@ def generate_html(json_path, output_dir, github_repo=None): |
2191 | 2233 | # Add commits as separate timeline items |
2192 | 2234 | for commit_ts, commit_hash, commit_msg, page_num, conv_idx in all_commits: |
2193 | 2235 | item_html = _macros.index_commit( |
2194 | | - commit_hash, commit_msg, commit_ts, _github_repo |
| 2236 | + commit_hash, commit_msg, commit_ts, get_github_repo() |
2195 | 2237 | ) |
2196 | 2238 | timeline_items.append((commit_ts, "commit", item_html)) |
2197 | 2239 |
|
@@ -2533,9 +2575,8 @@ def generate_html_from_session_data(session_data, output_dir, github_repo=None): |
2533 | 2575 | if github_repo: |
2534 | 2576 | click.echo(f"Auto-detected GitHub repo: {github_repo}") |
2535 | 2577 |
|
2536 | | - # Set module-level variable for render functions |
2537 | | - global _github_repo |
2538 | | - _github_repo = github_repo |
| 2578 | + # Set thread-safe context variable for render functions |
| 2579 | + set_github_repo(github_repo) |
2539 | 2580 |
|
2540 | 2581 | conversations = [] |
2541 | 2582 | current_conv = None |
@@ -2689,7 +2730,7 @@ def generate_html_from_session_data(session_data, output_dir, github_repo=None): |
2689 | 2730 | # Add commits as separate timeline items |
2690 | 2731 | for commit_ts, commit_hash, commit_msg, page_num, conv_idx in all_commits: |
2691 | 2732 | item_html = _macros.index_commit( |
2692 | | - commit_hash, commit_msg, commit_ts, _github_repo |
| 2733 | + commit_hash, commit_msg, commit_ts, get_github_repo() |
2693 | 2734 | ) |
2694 | 2735 | timeline_items.append((commit_ts, "commit", item_html)) |
2695 | 2736 |
|
|
0 commit comments