mermaid-trace 0.5.3.post0__py3-none-any.whl → 0.6.0.post0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mermaid_trace/cli.py CHANGED
@@ -232,7 +232,7 @@ def _create_handler(
232
232
  return Handler
233
233
 
234
234
 
235
- def serve(filename: str, port: int = 8000) -> None:
235
+ def serve(filename: str, port: int = 8000, master: bool = False) -> None:
236
236
  """
237
237
  Starts the local HTTP server and file watcher to preview a Mermaid diagram.
238
238
 
@@ -249,7 +249,30 @@ def serve(filename: str, port: int = 8000) -> None:
249
249
  Args:
250
250
  filename (str): The path to the .mmd file to serve.
251
251
  port (int): The port number to bind the server to (default: 8000).
252
+ master (bool): Whether to use the enhanced Master Preview Server (FastAPI + SSE).
252
253
  """
254
+ # 1. Enhanced Master Mode
255
+ if master:
256
+ try:
257
+ from .server import run_server, HAS_SERVER_DEPS
258
+
259
+ if HAS_SERVER_DEPS:
260
+ # For master mode, we watch the directory of the file
261
+ target_dir = os.path.dirname(os.path.abspath(filename))
262
+ if os.path.isdir(filename):
263
+ target_dir = os.path.abspath(filename)
264
+
265
+ # Open browser first
266
+ webbrowser.open(f"http://localhost:{port}")
267
+ run_server(target_dir, port)
268
+ return
269
+ else:
270
+ print(
271
+ "Warning: FastAPI/Uvicorn not found. Falling back to basic server."
272
+ )
273
+ except ImportError:
274
+ print("Warning: server module not found. Falling back to basic server.")
275
+
253
276
  # Create a Path object for robust file path handling
254
277
  path = Path(filename)
255
278
 
@@ -342,6 +365,11 @@ def main() -> None:
342
365
  serve_parser.add_argument(
343
366
  "--port", type=int, default=8000, help="Port to bind to (default: 8000)"
344
367
  )
368
+ serve_parser.add_argument(
369
+ "--master",
370
+ action="store_true",
371
+ help="Use enhanced Master Preview (requires FastAPI)",
372
+ )
345
373
 
346
374
  # Parse the arguments provided by the user
347
375
  args = parser.parse_args()
@@ -349,7 +377,7 @@ def main() -> None:
349
377
  # Dispatch logic
350
378
  if args.command == "serve":
351
379
  # Invoke the serve function with parsed arguments
352
- serve(args.file, args.port)
380
+ serve(args.file, args.port, args.master)
353
381
 
354
382
 
355
383
  if __name__ == "__main__":
@@ -2,3 +2,8 @@
2
2
  MermaidTrace integrations package.
3
3
  Contains middleware and adapters for third-party frameworks.
4
4
  """
5
+
6
+ from .fastapi import MermaidTraceMiddleware
7
+ from .langchain import MermaidTraceCallbackHandler
8
+
9
+ __all__ = ["MermaidTraceMiddleware", "MermaidTraceCallbackHandler"]
@@ -0,0 +1,312 @@
1
+ """
2
+ LangChain Integration Module for MermaidTrace.
3
+
4
+ This module provides a LangChain Callback Handler that allows you to automatically
5
+ generate Mermaid sequence diagrams for your LangChain chains, LLM calls, and tool usage.
6
+ """
7
+
8
+ from typing import Any, Dict, List, Optional, TYPE_CHECKING
9
+ import uuid
10
+
11
+ from ..core.events import FlowEvent
12
+ from ..core.context import LogContext
13
+ from ..core.decorators import get_flow_logger
14
+
15
+ if TYPE_CHECKING:
16
+ from langchain_core.callbacks import BaseCallbackHandler
17
+ from langchain_core.outputs import LLMResult
18
+ else:
19
+ try:
20
+ from langchain_core.callbacks import BaseCallbackHandler
21
+ from langchain_core.outputs import LLMResult
22
+ except ImportError:
23
+ BaseCallbackHandler = object
24
+ LLMResult = Any
25
+
26
+
27
+ class MermaidTraceCallbackHandler(BaseCallbackHandler):
28
+ """
29
+ LangChain Callback Handler that records execution flow as Mermaid sequence diagrams.
30
+
31
+ This handler intercepts LangChain events (Chain, LLM, Tool) and logs them as
32
+ FlowEvents, which are then processed by MermaidTrace to generate diagrams.
33
+ """
34
+
35
+ def __init__(self, host_name: str = "LangChain"):
36
+ """
37
+ Initialize the callback handler.
38
+
39
+ Args:
40
+ host_name (str): The name of the host participant in the diagram.
41
+ Defaults to "LangChain".
42
+ """
43
+ if BaseCallbackHandler is object:
44
+ raise ImportError(
45
+ "langchain-core is required to use MermaidTraceCallbackHandler. "
46
+ "Install it with `pip install langchain-core`."
47
+ )
48
+ self.host_name = host_name
49
+ self.logger = get_flow_logger()
50
+ self._participant_stack: List[str] = []
51
+
52
+ def _get_current_source(self) -> str:
53
+ if self._participant_stack:
54
+ return self._participant_stack[-1]
55
+ return str(LogContext.get("current_participant", self.host_name))
56
+
57
+ def on_chain_start(
58
+ self,
59
+ serialized: Optional[Dict[str, Any]],
60
+ inputs: Dict[str, Any],
61
+ **kwargs: Any,
62
+ ) -> None:
63
+ """Run when chain starts running."""
64
+ target = (
65
+ (serialized.get("name") if serialized else None)
66
+ or kwargs.get("name")
67
+ or "Chain"
68
+ )
69
+ source = self._get_current_source()
70
+
71
+ event = FlowEvent(
72
+ source=source,
73
+ target=target,
74
+ action="Run Chain",
75
+ message=f"Start Chain: {target}",
76
+ trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
77
+ params=str(inputs),
78
+ )
79
+ self.logger.info(
80
+ f"{source} -> {target}: {event.action}", extra={"flow_event": event}
81
+ )
82
+ self._participant_stack.append(target)
83
+
84
+ def on_chain_end(self, outputs: Dict[str, Any], **kwargs: Any) -> None:
85
+ """Run when chain ends running."""
86
+ if not self._participant_stack:
87
+ return
88
+
89
+ target = self._participant_stack.pop()
90
+ source = self._get_current_source()
91
+
92
+ event = FlowEvent(
93
+ source=target,
94
+ target=source,
95
+ action="Finish Chain",
96
+ message="Chain Complete",
97
+ trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
98
+ result=str(outputs),
99
+ is_return=True,
100
+ )
101
+ self.logger.info(
102
+ f"{target} -> {source}: {event.action}", extra={"flow_event": event}
103
+ )
104
+
105
+ def on_llm_start(
106
+ self, serialized: Optional[Dict[str, Any]], prompts: List[str], **kwargs: Any
107
+ ) -> None:
108
+ """Run when LLM starts running."""
109
+ target = (serialized.get("name") if serialized else None) or "LLM"
110
+ source = self._get_current_source()
111
+
112
+ event = FlowEvent(
113
+ source=source,
114
+ target=target,
115
+ action="Prompt",
116
+ message="LLM Request",
117
+ trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
118
+ params=str(prompts),
119
+ )
120
+ self.logger.info(
121
+ f"{source} -> {target}: {event.action}", extra={"flow_event": event}
122
+ )
123
+ self._participant_stack.append(target)
124
+
125
+ def on_chat_model_start(
126
+ self,
127
+ serialized: Optional[Dict[str, Any]],
128
+ messages: List[List[Any]],
129
+ **kwargs: Any,
130
+ ) -> None:
131
+ """Run when Chat Model starts running."""
132
+ target = (serialized.get("name") if serialized else None) or "ChatModel"
133
+ source = self._get_current_source()
134
+
135
+ event = FlowEvent(
136
+ source=source,
137
+ target=target,
138
+ action="Chat",
139
+ message="ChatModel Request",
140
+ trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
141
+ params=str(messages),
142
+ )
143
+ self.logger.info(
144
+ f"{source} -> {target}: {event.action}", extra={"flow_event": event}
145
+ )
146
+ self._participant_stack.append(target)
147
+
148
+ def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
149
+ """Run when LLM ends running."""
150
+ if not self._participant_stack:
151
+ return
152
+
153
+ source = self._participant_stack.pop()
154
+ target = self._get_current_source()
155
+
156
+ event = FlowEvent(
157
+ source=source,
158
+ target=target,
159
+ action="Response",
160
+ message="LLM/Chat Completion",
161
+ trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
162
+ result=str(response.generations),
163
+ is_return=True,
164
+ )
165
+ self.logger.info(
166
+ f"{source} -> {target}: {event.action}", extra={"flow_event": event}
167
+ )
168
+
169
+ def on_llm_error(self, error: BaseException, **kwargs: Any) -> None:
170
+ """Run when LLM errors."""
171
+ if not self._participant_stack:
172
+ return
173
+ target = self._participant_stack.pop()
174
+ source = self._get_current_source()
175
+ event = FlowEvent(
176
+ source=target,
177
+ target=source,
178
+ action="Error",
179
+ message=f"LLM Error: {type(error).__name__}",
180
+ trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
181
+ is_error=True,
182
+ error_message=str(error),
183
+ is_return=True,
184
+ )
185
+ self.logger.info(
186
+ f"{target} -> {source}: {event.action}", extra={"flow_event": event}
187
+ )
188
+
189
+ def on_retriever_start(
190
+ self,
191
+ serialized: Optional[Dict[str, Any]],
192
+ query: str,
193
+ **kwargs: Any,
194
+ ) -> None:
195
+ """Run when Retriever starts running."""
196
+ target = (serialized.get("name") if serialized else None) or "Retriever"
197
+ source = self._get_current_source()
198
+
199
+ event = FlowEvent(
200
+ source=source,
201
+ target=target,
202
+ action="Retrieve",
203
+ message=f"Query: {query[:50]}...",
204
+ trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
205
+ params=query,
206
+ )
207
+ self.logger.info(
208
+ f"{source} -> {target}: {event.action}", extra={"flow_event": event}
209
+ )
210
+ self._participant_stack.append(target)
211
+
212
+ def on_retriever_end(self, documents: List[Any], **kwargs: Any) -> Any: # type: ignore[override]
213
+ """Run when Retriever ends running."""
214
+ if not self._participant_stack:
215
+ return
216
+
217
+ target = self._participant_stack.pop()
218
+ source = self._get_current_source()
219
+
220
+ event = FlowEvent(
221
+ source=target,
222
+ target=source,
223
+ action="Documents",
224
+ message=f"Retrieved {len(documents)} docs",
225
+ trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
226
+ result=f"Count: {len(documents)}",
227
+ is_return=True,
228
+ )
229
+ self.logger.info(
230
+ f"{target} -> {source}: {event.action}", extra={"flow_event": event}
231
+ )
232
+
233
+ def on_tool_start(
234
+ self, serialized: Optional[Dict[str, Any]], input_str: str, **kwargs: Any
235
+ ) -> None:
236
+ """Run when tool starts running."""
237
+ target = (serialized.get("name") if serialized else None) or "Tool"
238
+ source = self._get_current_source()
239
+
240
+ event = FlowEvent(
241
+ source=source,
242
+ target=target,
243
+ action="Call Tool",
244
+ message=f"Tool: {target}",
245
+ trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
246
+ params=input_str,
247
+ )
248
+ self.logger.info(
249
+ f"{source} -> {target}: {event.action}", extra={"flow_event": event}
250
+ )
251
+ self._participant_stack.append(target)
252
+
253
+ def on_tool_end(self, output: Any, **kwargs: Any) -> None:
254
+ """Run when tool ends running."""
255
+ if not self._participant_stack:
256
+ return
257
+
258
+ target = self._participant_stack.pop()
259
+ source = self._get_current_source()
260
+
261
+ event = FlowEvent(
262
+ source=target,
263
+ target=source,
264
+ action="Finish Tool",
265
+ message="Tool Complete",
266
+ trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
267
+ result=str(output),
268
+ is_return=True,
269
+ )
270
+ self.logger.info(
271
+ f"{target} -> {source}: {event.action}", extra={"flow_event": event}
272
+ )
273
+
274
+ def on_chain_error(self, error: BaseException, **kwargs: Any) -> None:
275
+ """Run when chain errors."""
276
+ if not self._participant_stack:
277
+ return
278
+ target = self._participant_stack.pop()
279
+ source = self._get_current_source()
280
+ event = FlowEvent(
281
+ source=target,
282
+ target=source,
283
+ action="Error",
284
+ message=f"Chain Error: {type(error).__name__}",
285
+ trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
286
+ is_error=True,
287
+ error_message=str(error),
288
+ is_return=True,
289
+ )
290
+ self.logger.info(
291
+ f"{target} -> {source}: {event.action}", extra={"flow_event": event}
292
+ )
293
+
294
+ def on_tool_error(self, error: BaseException, **kwargs: Any) -> None:
295
+ """Run when tool errors."""
296
+ if not self._participant_stack:
297
+ return
298
+ target = self._participant_stack.pop()
299
+ source = self._get_current_source()
300
+ event = FlowEvent(
301
+ source=target,
302
+ target=source,
303
+ action="Error",
304
+ message=f"Tool Error: {type(error).__name__}",
305
+ trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
306
+ is_error=True,
307
+ error_message=str(error),
308
+ is_return=True,
309
+ )
310
+ self.logger.info(
311
+ f"{target} -> {source}: {event.action}", extra={"flow_event": event}
312
+ )
@@ -0,0 +1,406 @@
1
+ """
2
+ Enhanced Web Server for MermaidTrace.
3
+
4
+ This module provides a robust, real-time preview server using FastAPI and Server-Sent Events (SSE).
5
+ It monitors .mmd files and pushes updates to the browser instantly.
6
+ """
7
+
8
+ import asyncio
9
+ import os
10
+ import json
11
+ from pathlib import Path
12
+ from typing import AsyncGenerator, Set, Any
13
+
14
+ try:
15
+ from fastapi import FastAPI, Request, HTTPException
16
+ from fastapi.responses import HTMLResponse, StreamingResponse
17
+ import uvicorn
18
+
19
+ HAS_SERVER_DEPS = True
20
+ except ImportError:
21
+ HAS_SERVER_DEPS = False
22
+
23
+ try:
24
+ from watchdog.observers import Observer
25
+ from watchdog.events import FileSystemEventHandler
26
+
27
+ HAS_WATCHDOG = True
28
+ except ImportError:
29
+ HAS_WATCHDOG = False
30
+
31
+ app = FastAPI(title="MermaidTrace Preview Server")
32
+
33
+
34
+ # Global state to manage connected clients for SSE
35
+ class ConnectionManager:
36
+ def __init__(self) -> None:
37
+ self.active_connections: Set[asyncio.Queue[dict[str, Any]]] = set()
38
+
39
+ async def subscribe(self) -> asyncio.Queue[dict[str, Any]]:
40
+ queue: asyncio.Queue[dict[str, Any]] = asyncio.Queue()
41
+ self.active_connections.add(queue)
42
+ return queue
43
+
44
+ def unsubscribe(self, queue: asyncio.Queue[dict[str, Any]]) -> None:
45
+ self.active_connections.remove(queue)
46
+
47
+ async def broadcast(self, data: dict[str, Any]) -> None:
48
+ for queue in self.active_connections:
49
+ await queue.put(data)
50
+
51
+
52
+ manager = ConnectionManager()
53
+
54
+ # HTML Template with enhanced UI
55
+ HTML_TEMPLATE = """
56
+ <!DOCTYPE html>
57
+ <html lang="en">
58
+ <head>
59
+ <meta charset="UTF-8">
60
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
61
+ <title>MermaidTrace Master Preview</title>
62
+ <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
63
+ <script src="https://cdn.jsdelivr.net/npm/svg-pan-zoom@3.6.1/dist/svg-pan-zoom.min.js"></script>
64
+ <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
65
+ <style>
66
+ body { background-color: #f8fafc; }
67
+ .mermaid { background: white; }
68
+ #diagram-container {
69
+ height: calc(100vh - 10rem);
70
+ overflow: hidden;
71
+ position: relative;
72
+ background-image: radial-gradient(#e2e8f0 1px, transparent 1px);
73
+ background-size: 20px 20px;
74
+ display: flex;
75
+ flex-direction: column;
76
+ }
77
+ #svg-wrapper {
78
+ flex: 1;
79
+ width: 100%;
80
+ height: 100%;
81
+ cursor: grab;
82
+ display: flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+ }
86
+ #svg-wrapper:active { cursor: grabbing; }
87
+ .sidebar-item:hover { background-color: #e2e8f0; }
88
+ .sidebar-item.active { background-color: #3b82f6; color: white; }
89
+ /* Ensure mermaid SVG doesn't have max-width constraints */
90
+ #mermaid-graph {
91
+ width: 100%;
92
+ height: 100%;
93
+ display: flex;
94
+ align-items: center;
95
+ justify-content: center;
96
+ }
97
+ #mermaid-graph svg {
98
+ max-width: none !important;
99
+ max-height: none !important;
100
+ }
101
+ </style>
102
+ </head>
103
+ <body class="flex flex-col h-screen">
104
+ <!-- Header -->
105
+ <header class="bg-white border-b border-gray-200 px-6 py-4 flex justify-between items-center shadow-sm">
106
+ <div class="flex items-center space-x-3">
107
+ <div class="bg-blue-600 text-white p-2 rounded-lg">
108
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
109
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
110
+ </svg>
111
+ </div>
112
+ <h1 class="text-xl font-bold text-gray-800">MermaidTrace <span class="text-blue-600">Master</span></h1>
113
+ </div>
114
+ <div class="flex items-center space-x-4">
115
+ <span id="status-badge" class="px-3 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800">Live Connected</span>
116
+ <button onclick="resetZoom()" class="text-gray-600 hover:text-blue-600 transition">
117
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg>
118
+ </button>
119
+ <button onclick="downloadSVG()" class="text-gray-600 hover:text-blue-600 transition">
120
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path></svg>
121
+ </button>
122
+ </div>
123
+ </header>
124
+
125
+ <div class="flex flex-1 overflow-hidden">
126
+ <!-- Sidebar -->
127
+ <aside class="w-64 bg-white border-r border-gray-200 overflow-y-auto hidden md:block">
128
+ <div class="p-4 border-b border-gray-100">
129
+ <h2 class="text-xs font-semibold text-gray-500 uppercase tracking-wider">Trace Files</h2>
130
+ </div>
131
+ <nav id="file-list" class="p-2 space-y-1">
132
+ <!-- File items will be injected here -->
133
+ </nav>
134
+ </aside>
135
+
136
+ <!-- Main Content -->
137
+ <main class="flex-1 flex flex-col p-6 overflow-hidden">
138
+ <div class="mb-4 flex justify-between items-end">
139
+ <div>
140
+ <h2 id="current-filename" class="text-2xl font-bold text-gray-900">Select a file</h2>
141
+ <p id="last-updated" class="text-sm text-gray-500 mt-1">Ready to visualize</p>
142
+ </div>
143
+ </div>
144
+
145
+ <div id="diagram-container" class="flex-1 bg-white rounded-xl shadow-inner border border-gray-200 overflow-hidden">
146
+ <div id="svg-wrapper">
147
+ <div class="mermaid" id="mermaid-graph">
148
+ sequenceDiagram
149
+ Note over Server, Client: Waiting for trace data...
150
+ </div>
151
+ </div>
152
+ </div>
153
+ </main>
154
+ </div>
155
+
156
+ <script>
157
+ let panZoomInstance = null;
158
+ let currentFile = null;
159
+
160
+ function initMermaid() {
161
+ if (typeof mermaid === 'undefined') {
162
+ console.log("Waiting for mermaid...");
163
+ setTimeout(initMermaid, 100);
164
+ return;
165
+ }
166
+
167
+ mermaid.initialize({
168
+ startOnLoad: false,
169
+ theme: 'default',
170
+ securityLevel: 'loose',
171
+ useMaxWidth: false,
172
+ sequence: {
173
+ showSequenceNumbers: true,
174
+ useMaxWidth: false,
175
+ bottomMarginAdjustment: 1
176
+ }
177
+ });
178
+
179
+ // Initialize
180
+ updateFileList();
181
+ }
182
+
183
+ async function loadFile(filename) {
184
+ if (!filename) return;
185
+ currentFile = filename;
186
+
187
+ // Update active state in sidebar
188
+ document.querySelectorAll('.sidebar-item').forEach(el => {
189
+ if (el.dataset.filename === filename) el.classList.add('active');
190
+ else el.classList.remove('active');
191
+ });
192
+
193
+ try {
194
+ const response = await fetch(`/api/file?name=${encodeURIComponent(filename)}`);
195
+ const data = await response.json();
196
+
197
+ document.getElementById('current-filename').textContent = filename;
198
+ renderDiagram(data.content);
199
+ updateTimestamp();
200
+ } catch (err) {
201
+ console.error("Failed to load file:", err);
202
+ }
203
+ }
204
+
205
+ async function renderDiagram(content) {
206
+ const graphDiv = document.getElementById('mermaid-graph');
207
+ graphDiv.removeAttribute('data-processed');
208
+ // Clean up previous SVG before rendering new one
209
+ graphDiv.innerHTML = content;
210
+
211
+ try {
212
+ const { svg } = await mermaid.render('mermaid-svg-' + Date.now(), content);
213
+ graphDiv.innerHTML = svg;
214
+ setupPanZoom();
215
+ } catch (err) {
216
+ console.error("Mermaid render error:", err);
217
+ }
218
+ }
219
+
220
+ function setupPanZoom() {
221
+ if (panZoomInstance) {
222
+ panZoomInstance.destroy();
223
+ panZoomInstance = null;
224
+ }
225
+ const svg = document.querySelector('#mermaid-graph svg');
226
+ if (svg) {
227
+ // Ensure SVG takes up all available space for the pan-zoom container
228
+ svg.style.width = '100%';
229
+ svg.style.height = '100%';
230
+ svg.style.maxWidth = 'none';
231
+ svg.style.maxHeight = 'none';
232
+
233
+ // Clear explicit width/height attributes that might conflict with 'fit'
234
+ svg.removeAttribute('width');
235
+ svg.removeAttribute('height');
236
+
237
+ panZoomInstance = svgPanZoom(svg, {
238
+ zoomEnabled: true,
239
+ controlIconsEnabled: false,
240
+ fit: true,
241
+ center: true,
242
+ minZoom: 0.1,
243
+ maxZoom: 20,
244
+ zoomScaleSensitivity: 0.2
245
+ });
246
+
247
+ // Auto fit on window resize
248
+ window.addEventListener('resize', () => {
249
+ if (panZoomInstance) {
250
+ panZoomInstance.resize();
251
+ panZoomInstance.fit();
252
+ panZoomInstance.center();
253
+ }
254
+ });
255
+ }
256
+ }
257
+
258
+ function resetZoom() {
259
+ if (panZoomInstance) {
260
+ panZoomInstance.fit();
261
+ panZoomInstance.center();
262
+ }
263
+ }
264
+
265
+ function updateTimestamp() {
266
+ const now = new Date();
267
+ document.getElementById('last-updated').textContent = `Last updated: ${now.toLocaleTimeString()}`;
268
+ }
269
+
270
+ async function updateFileList() {
271
+ const response = await fetch('/api/files');
272
+ const files = await response.json();
273
+ const list = document.getElementById('file-list');
274
+ list.innerHTML = '';
275
+
276
+ files.forEach(f => {
277
+ const item = document.createElement('a');
278
+ item.href = "#";
279
+ item.className = `sidebar-item block px-3 py-2 text-sm font-medium rounded-md transition ${f === currentFile ? 'active' : 'text-gray-700'}`;
280
+ item.dataset.filename = f;
281
+ item.textContent = f;
282
+ item.onclick = (e) => {
283
+ e.preventDefault();
284
+ loadFile(f);
285
+ };
286
+ list.appendChild(item);
287
+ });
288
+
289
+ if (!currentFile && files.length > 0) {
290
+ loadFile(files[0]);
291
+ }
292
+ }
293
+
294
+ // SSE Connection for real-time updates
295
+ const eventSource = new EventSource("/events");
296
+ eventSource.onmessage = (event) => {
297
+ const data = JSON.parse(event.data);
298
+ if (data.type === "update" && data.filename === currentFile) {
299
+ console.log("File update received:", data.filename);
300
+ loadFile(data.filename);
301
+ } else if (data.type === "refresh_list") {
302
+ updateFileList();
303
+ }
304
+ };
305
+
306
+ eventSource.onerror = () => {
307
+ document.getElementById('status-badge').className = "px-3 py-1 rounded-full text-xs font-medium bg-red-100 text-red-800";
308
+ document.getElementById('status-badge').textContent = "Connection Lost";
309
+ };
310
+
311
+ // Start Initialization
312
+ initMermaid();
313
+
314
+ function downloadSVG() {
315
+ const svg = document.querySelector('#diagram-container svg');
316
+ if (!svg) return;
317
+ const serializer = new XMLSerializer();
318
+ let source = serializer.serializeToString(svg);
319
+ if(!source.match(/^<svg[^>]+xmlns="http\\:\\/\\/www\\.w3\\.org\\/2000\\/svg"/)){
320
+ source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
321
+ }
322
+ if(!source.match(/^<svg[^>]+xmlns\\:xlink="http\\:\\/\\/www\\.w3\\.org\\/1999\\/xlink"/)){
323
+ source = source.replace(/^<svg/, '<svg xmlns:xlink="http://www.w3.org/1999/xlink"');
324
+ }
325
+ source = '<?xml version="1.0" standalone="no"?>\\r\\n' + source;
326
+ const url = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(source);
327
+ const link = document.createElement("a");
328
+ link.href = url;
329
+ link.download = `${currentFile || 'diagram'}.svg`;
330
+ link.click();
331
+ }
332
+ </script>
333
+ </body>
334
+ </html>
335
+ """
336
+
337
+ # Global directory to watch
338
+ watch_dir: Path = Path(".")
339
+
340
+
341
+ @app.get("/", response_class=HTMLResponse)
342
+ async def get_index() -> str:
343
+ return HTML_TEMPLATE
344
+
345
+
346
+ @app.get("/api/files")
347
+ async def list_files() -> list[str]:
348
+ files = sorted([f.name for f in watch_dir.glob("*.mmd")])
349
+ return files
350
+
351
+
352
+ @app.get("/api/file")
353
+ async def get_file_content(name: str) -> dict[str, str]:
354
+ file_path = watch_dir / name
355
+ if not file_path.exists() or not str(file_path).endswith(".mmd"):
356
+ raise HTTPException(status_code=404, detail="File not found")
357
+ return {"content": file_path.read_text(encoding="utf-8")}
358
+
359
+
360
+ @app.get("/events")
361
+ async def sse_endpoint(request: Request) -> StreamingResponse:
362
+ async def event_generator() -> AsyncGenerator[str, None]:
363
+ queue = await manager.subscribe()
364
+ try:
365
+ while True:
366
+ if await request.is_disconnected():
367
+ break
368
+ data = await queue.get()
369
+ yield f"data: {json.dumps(data)}\\n\\n"
370
+ finally:
371
+ manager.unsubscribe(queue)
372
+
373
+ return StreamingResponse(event_generator(), media_type="text/event-stream")
374
+
375
+
376
+ def run_server(directory: str, port: int = 8000) -> None:
377
+ global watch_dir
378
+ watch_dir = Path(directory).resolve()
379
+
380
+ if not HAS_SERVER_DEPS:
381
+ print("Error: FastAPI, Uvicorn are required for the enhanced server.")
382
+ print("Install them with: pip install fastapi uvicorn")
383
+ return
384
+
385
+ # Start Watchdog
386
+ if HAS_WATCHDOG:
387
+
388
+ class Handler(FileSystemEventHandler):
389
+ def on_modified(self, event: Any) -> None:
390
+ if not event.is_directory and event.src_path.endswith(".mmd"):
391
+ filename = os.path.basename(event.src_path)
392
+ asyncio.run(
393
+ manager.broadcast({"type": "update", "filename": filename})
394
+ )
395
+
396
+ def on_created(self, event: Any) -> None:
397
+ if not event.is_directory and event.src_path.endswith(".mmd"):
398
+ asyncio.run(manager.broadcast({"type": "refresh_list"}))
399
+
400
+ observer = Observer()
401
+ observer.schedule(Handler(), str(watch_dir), recursive=False)
402
+ observer.start()
403
+ print(f"[*] Watching directory: {watch_dir}")
404
+
405
+ print(f"[*] Starting Master Preview Server at http://localhost:{port}")
406
+ uvicorn.run(app, host="0.0.0.0", port=port, log_level="error")
@@ -0,0 +1,272 @@
1
+ Metadata-Version: 2.4
2
+ Name: mermaid-trace
3
+ Version: 0.6.0.post0
4
+ Summary: Visualize your Python code execution flow as Mermaid Sequence Diagrams.
5
+ Project-URL: Documentation, https://github.com/xt765/mermaid-trace#readme
6
+ Project-URL: Changelog, https://github.com/xt765/mermaid-trace/blob/main/docs/en/CHANGELOG.md
7
+ Project-URL: Issues, https://github.com/xt765/mermaid-trace/issues
8
+ Project-URL: Source, https://github.com/xt765/mermaid-trace
9
+ Author-email: xt765 <xt765@foxmail.com>
10
+ License: MIT License
11
+
12
+ Copyright (c) 2026 xt765
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+ License-File: LICENSE
32
+ Keywords: asyncio,logging,mermaid,mermaid-trace,sequence-diagram,trace,visualization
33
+ Classifier: Development Status :: 4 - Beta
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Operating System :: OS Independent
37
+ Classifier: Programming Language :: Python
38
+ Classifier: Programming Language :: Python :: 3.10
39
+ Classifier: Programming Language :: Python :: 3.11
40
+ Classifier: Programming Language :: Python :: 3.12
41
+ Classifier: Programming Language :: Python :: 3.13
42
+ Classifier: Programming Language :: Python :: Implementation :: CPython
43
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
44
+ Classifier: Topic :: Software Development :: Debuggers
45
+ Classifier: Topic :: System :: Logging
46
+ Requires-Python: >=3.10
47
+ Requires-Dist: typing-extensions>=4.0.0
48
+ Requires-Dist: watchdog>=2.0.0
49
+ Provides-Extra: all
50
+ Requires-Dist: fastapi>=0.100.0; extra == 'all'
51
+ Requires-Dist: langchain-core>=0.1.0; extra == 'all'
52
+ Provides-Extra: dev
53
+ Requires-Dist: fastapi>=0.100.0; extra == 'dev'
54
+ Requires-Dist: httpx; extra == 'dev'
55
+ Requires-Dist: langchain-core>=0.1.0; extra == 'dev'
56
+ Requires-Dist: mypy; extra == 'dev'
57
+ Requires-Dist: pytest; extra == 'dev'
58
+ Requires-Dist: pytest-asyncio; extra == 'dev'
59
+ Requires-Dist: pytest-cov; extra == 'dev'
60
+ Requires-Dist: ruff; extra == 'dev'
61
+ Provides-Extra: fastapi
62
+ Requires-Dist: fastapi>=0.100.0; extra == 'fastapi'
63
+ Provides-Extra: langchain
64
+ Requires-Dist: langchain-core>=0.1.0; extra == 'langchain'
65
+ Description-Content-Type: text/markdown
66
+
67
+ # MermaidTrace: Visualize Your Python Code Logic
68
+
69
+ **Stop drowning in cryptic logs. One line of code to transform complex execution logic into clear Mermaid sequence diagrams.**
70
+
71
+ 🌐 **Language**: [English](README.md) | [中文](README_CN.md)
72
+
73
+ [![CSDN Blog](https://img.shields.io/badge/CSDN-玄同765-orange?style=flat-square&logo=csdn)](https://blog.csdn.net/Yunyi_Chi)
74
+ [![GitHub](https://img.shields.io/badge/GitHub-mermaid--trace-black?style=flat-square&logo=github)](https://github.com/xt765/mermaid-trace)
75
+ [![Gitee](https://img.shields.io/badge/Gitee-mermaid--trace-red?style=flat-square&logo=gitee)](https://gitee.com/xt765/mermaid-trace)
76
+ [![PyPI version](https://img.shields.io/pypi/v/mermaid-trace.svg?style=flat-square&color=blue)](https://pypi.org/project/mermaid-trace/)
77
+ [![Python Versions](https://img.shields.io/pypi/pyversions/mermaid-trace.svg?style=flat-square&color=blue)](https://pypi.org/project/mermaid-trace/)
78
+ [![License](https://img.shields.io/github/license/xt765/mermaid-trace?style=flat-square)](LICENSE)
79
+ [![CI Status](https://img.shields.io/github/actions/workflow/status/xt765/mermaid-trace/ci.yml?style=flat-square&label=CI)](https://github.com/xt765/mermaid-trace/actions/workflows/ci.yml)
80
+ [![Codecov](https://img.shields.io/codecov/c/github/xt765/mermaid-trace?style=flat-square&logo=codecov)](https://codecov.io/gh/xt765/mermaid-trace)
81
+
82
+ ---
83
+
84
+ ## ⚡️ Understand MermaidTrace in 5 Seconds
85
+
86
+ #### 1. Original Code (15+ lines)
87
+ ```python
88
+ @trace(source="User", target="OrderSys")
89
+ def create_order(user_id, items):
90
+ # Complex business logic
91
+ if not check_inventory(items):
92
+ return "Out of Stock"
93
+
94
+ # Nested logic calls
95
+ price = calculate_price(items)
96
+ discount = get_discount(user_id)
97
+ final = price - discount
98
+
99
+ # External service interactions
100
+ res = pay_service.process(final)
101
+ if res.success:
102
+ update_stock(items)
103
+ send_notif(user_id)
104
+ return "Success"
105
+ return "Failed"
106
+ ```
107
+
108
+ #### 2. Auto-Generated Sequence Diagram
109
+ ```mermaid
110
+ sequenceDiagram
111
+ autonumber
112
+ User->>OrderSys: create_order(user_id, items)
113
+ activate OrderSys
114
+ OrderSys->>Inventory: check_inventory(items)
115
+ Inventory-->>OrderSys: True
116
+ OrderSys->>Pricing: calculate_price(items)
117
+ Pricing-->>OrderSys: 100.0
118
+ OrderSys->>UserDB: get_discount(user_id)
119
+ UserDB-->>OrderSys: 5.0
120
+ OrderSys->>PayService: process(95.0)
121
+ activate PayService
122
+ PayService-->>OrderSys: success
123
+ deactivate PayService
124
+ OrderSys->>Inventory: update_stock(items)
125
+ OrderSys->>Notification: send_notif(user_id)
126
+ OrderSys-->>User: "Success"
127
+ deactivate OrderSys
128
+ ```
129
+
130
+ ---
131
+
132
+ ## 🚀 Dynamic Demo & Online Tryout
133
+
134
+ ### 🎬 Quick Demo
135
+
136
+ ![MermaidTrace Master Preview](docs/images/master_preview.png)
137
+
138
+ *(Master Preview: Multi-file browsing, live-reload, and interactive pan/zoom)*
139
+
140
+ ```mermaid
141
+ sequenceDiagram
142
+ participant CLI as mermaid-trace CLI
143
+ participant App as Python App
144
+ participant Web as Live Preview
145
+
146
+ Note over CLI, Web: Enable Live Preview Mode
147
+ CLI->>Web: Start HTTP Server (localhost:8000)
148
+ App->>App: Run Logic (with @trace decorator)
149
+ App->>App: Auto-update flow.mmd
150
+ Web->>Web: File Change Detected (Hot Reload)
151
+ Web-->>CLI: Render Latest Diagram
152
+ ```
153
+ *(From adding decorators to browser live preview in 10 seconds)*
154
+
155
+ ### 🛠️ Try Online (Google Colab)
156
+
157
+ No local setup required. Experience core features in your browser:
158
+
159
+ [![Open In Colab](https://img.shields.io/badge/Colab-Open%20in%20Colab-blue?style=flat&logo=google-colab&logoColor=white)](https://colab.research.google.com/github/xt765/mermaid-trace/blob/main/examples/MermaidTrace_Demo.ipynb)
160
+
161
+ ---
162
+
163
+ ## 🎯 Why MermaidTrace? (Use Cases)
164
+
165
+ ### 1. Master "Legacy" Codebases
166
+ **Pain**: Taking over a complex, undocumented legacy project with tangled function calls.
167
+ **Solution**: Add `@trace_class` or `@trace` to entry points and run the code once.
168
+ **Value**: Instantly generate a complete execution path map to understand the architecture.
169
+
170
+ ### 2. Automated Technical Docs
171
+ **Pain**: Manual sequence diagrams are time-consuming and quickly become outdated.
172
+ **Solution**: Integrate MermaidTrace during development.
173
+ **Value**: Diagrams stay 100% in sync with your code logic automatically.
174
+
175
+ ### 3. Debug Complex Recursion & Concurrency
176
+ **Pain**: Nested calls or async tasks produce interleaved logs that are impossible to read.
177
+ **Solution**: Use built-in async support and intelligent collapsing.
178
+ **Value**: Visualize recursion depth and concurrency flow to pinpoint logic bottlenecks.
179
+
180
+ ---
181
+
182
+ ## 🚀 Quick Start in 3 Steps
183
+
184
+ ### 1. Install
185
+ ```bash
186
+ pip install mermaid-trace
187
+ ```
188
+
189
+ ### 2. Add Decorators
190
+ ```python
191
+ from mermaid_trace import trace, configure_flow
192
+
193
+ # Configure output file
194
+ configure_flow("my_flow.mmd")
195
+
196
+ @trace(source="User", target="AuthService")
197
+ def login(username):
198
+ return verify_db(username)
199
+
200
+ @trace(source="AuthService", target="DB")
201
+ def verify_db(username):
202
+ return True
203
+
204
+ login("admin")
205
+ ```
206
+
207
+ ### 3. View Diagram
208
+
209
+ Run the built-in CLI tool to preview in real-time (with hot-reload):
210
+
211
+ ```bash
212
+ # Basic preview
213
+ mermaid-trace serve my_flow.mmd
214
+
215
+ # Master mode (Directory browsing, zoom, multi-file switching)
216
+ mermaid-trace serve . --master
217
+ # Or preview a specific file in Master mode
218
+ mermaid-trace serve .\mermaid_diagrams\examples\08-log-rotation.mmd --master
219
+ ```
220
+
221
+ ### 🔗 LangChain Integration
222
+ Visualize LLM chains, agents, and RAG retrieval with a single handler:
223
+ ```python
224
+ from mermaid_trace.integrations.langchain import MermaidTraceCallbackHandler
225
+
226
+ handler = MermaidTraceCallbackHandler(host_name="MyAIApp")
227
+ # Pass to any LangChain object
228
+ chain.invoke({"input": "..."}, config={"callbacks": [handler]})
229
+ ```
230
+
231
+ ---
232
+
233
+ ## ✨ Key Features
234
+
235
+ - **Decorator-Driven**: Simply add `@trace` or `@trace_interaction` to functions.
236
+ - **Auto-Instrumentation**: Use `@trace_class` to trace a whole class at once.
237
+ - **Third-Party Patching**: Use `patch_object` to trace calls inside external libraries.
238
+ - **Async Support**: Seamlessly works with `asyncio` coroutines and concurrency.
239
+ - **Enhanced Web UI**: Interactive preview server with file browsing, auto-reload, and pan/zoom support (use `--master`).
240
+ - **Intelligent Collapsing**: Automatically collapses repetitive calls and identifies loops.
241
+ - **FastAPI Integration**: Middleware for zero-config HTTP request tracing.
242
+ - **LangChain Integration**: Callback Handler for LLM chains and agent visualization.
243
+ - **Detailed Exceptions**: Captures full stack traces for errors, displayed in the diagram.
244
+
245
+ ---
246
+
247
+ ## 📚 Documentation
248
+
249
+ ### Core Documentation
250
+
251
+ [User Guide](docs/en/USER_GUIDE.md) · [API Reference](docs/en/API.md) · [Contributing Guidelines](docs/en/CONTRIBUTING.md) · [Changelog](docs/en/CHANGELOG.md) · [License](LICENSE)
252
+
253
+ ### Code Comment Documents
254
+
255
+ | Category | Links |
256
+ | :--- | :--- |
257
+ | **Core Modules** | [Context](docs/en/code_comments/src/mermaid_trace/core/context.md) · [Decorators](docs/en/code_comments/src/mermaid_trace/core/decorators.md) · [Events](docs/en/code_comments/src/mermaid_trace/core/events.md) · [Formatter](docs/en/code_comments/src/mermaid_trace/core/formatter.md) |
258
+ | **Handlers** | [Async Handler](docs/en/code_comments/src/mermaid_trace/handlers/async_handler.md) · [Mermaid Handler](docs/en/code_comments/src/mermaid_trace/handlers/mermaid_handler.md) |
259
+ | **Integrations** | [FastAPI](docs/en/code_comments/src/mermaid_trace/integrations/fastapi.md) |
260
+ | **Others** | [init](docs/en/code_comments/src/mermaid_trace/__init__.md) · [CLI](docs/en/code_comments/src/mermaid_trace/cli.md) |
261
+
262
+ ---
263
+
264
+ ## 🤝 Contributing
265
+
266
+ We welcome contributions! Please see [CONTRIBUTING.md](docs/en/CONTRIBUTING.md) for details.
267
+
268
+ ---
269
+
270
+ ## 📄 License
271
+
272
+ MIT
@@ -1,6 +1,7 @@
1
1
  mermaid_trace/__init__.py,sha256=EnAsz6R6ag4dtabdHEg1wdB2k7V1uwmwpYTTC41GQX4,6721
2
- mermaid_trace/cli.py,sha256=g7Qriiox9YqfJutUntE1a7GwByg62wGwQcLfpvicjT8,15076
2
+ mermaid_trace/cli.py,sha256=FCbm9kHf7B74bvkRv_k7stTeG7S7GqNSITmUQW1kJEA,16173
3
3
  mermaid_trace/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ mermaid_trace/server.py,sha256=1nNIS6pNAn3_XCwuoUkKIOsq4GJK5o33QFzv-hh-IJY,15458
4
5
  mermaid_trace/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
6
  mermaid_trace/core/config.py,sha256=akTOc3m_bzcZ7e1Qtf1n5Ct9Dj8XPTTcWzATG3-qJHw,1890
6
7
  mermaid_trace/core/context.py,sha256=wr-Ys3c6PsYCtbPUsQTrnaciSkP-fofxOewo4oZ27QM,8556
@@ -10,10 +11,11 @@ mermaid_trace/core/formatter.py,sha256=3KEq236E3GAj_j53MuBM2ndWm7Kx4KeywJWmoY5o5
10
11
  mermaid_trace/core/utils.py,sha256=FagsMYLZcbmOgKmRo3v9tcRNVIpuc1YEKTAjcRk7keY,3410
11
12
  mermaid_trace/handlers/async_handler.py,sha256=WmLcULXHasapt7-W0p_Eltjmwvlq6r6BS6pViuseZ4w,8252
12
13
  mermaid_trace/handlers/mermaid_handler.py,sha256=Czar6JYSPTu5L1fQC0I8v1UpToD6aE_r96IkPpRSByw,6142
13
- mermaid_trace/integrations/__init__.py,sha256=uU_8E1tlP327Fn79kvzlgEdoiIpQLsWJl3BgyqgsFMQ,104
14
+ mermaid_trace/integrations/__init__.py,sha256=stWAHIL1zv21foaXFXZ_3SLxJ8icN0YFGWOvvHERCDI,269
14
15
  mermaid_trace/integrations/fastapi.py,sha256=6LRs4Z508l38ymfj5SP39vOV6OLfYLRkpxyL_SxvudM,9483
15
- mermaid_trace-0.5.3.post0.dist-info/METADATA,sha256=FR1kgM-wlTzrFasLpIeqbWbV_X3h3TwwUkxQCvgbvo0,9959
16
- mermaid_trace-0.5.3.post0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
17
- mermaid_trace-0.5.3.post0.dist-info/entry_points.txt,sha256=WS57KT_870v0A4B87QDjQUqJcddMQxbCQyYeczDAX34,57
18
- mermaid_trace-0.5.3.post0.dist-info/licenses/LICENSE,sha256=BrBog1Etiq9PdWy0SVQNVByIMD9ss4Edz-R0oXt49zA,1062
19
- mermaid_trace-0.5.3.post0.dist-info/RECORD,,
16
+ mermaid_trace/integrations/langchain.py,sha256=n1M2-OYNMsKiMpfGj04cs_dLyn2t5T9APmgAZsqqm5Q,10598
17
+ mermaid_trace-0.6.0.post0.dist-info/METADATA,sha256=kIvprKuj4jiOxpTaqumiqclh3euhw_7BkeZWg3i-YyE,10954
18
+ mermaid_trace-0.6.0.post0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
19
+ mermaid_trace-0.6.0.post0.dist-info/entry_points.txt,sha256=WS57KT_870v0A4B87QDjQUqJcddMQxbCQyYeczDAX34,57
20
+ mermaid_trace-0.6.0.post0.dist-info/licenses/LICENSE,sha256=BrBog1Etiq9PdWy0SVQNVByIMD9ss4Edz-R0oXt49zA,1062
21
+ mermaid_trace-0.6.0.post0.dist-info/RECORD,,
@@ -1,232 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: mermaid-trace
3
- Version: 0.5.3.post0
4
- Summary: Visualize your Python code execution flow as Mermaid Sequence Diagrams.
5
- Project-URL: Documentation, https://github.com/xt765/mermaid-trace#readme
6
- Project-URL: Changelog, https://github.com/xt765/mermaid-trace/blob/main/docs/en/CHANGELOG.md
7
- Project-URL: Issues, https://github.com/xt765/mermaid-trace/issues
8
- Project-URL: Source, https://github.com/xt765/mermaid-trace
9
- Author-email: xt765 <xt765@foxmail.com>
10
- License: MIT License
11
-
12
- Copyright (c) 2026 xt765
13
-
14
- Permission is hereby granted, free of charge, to any person obtaining a copy
15
- of this software and associated documentation files (the "Software"), to deal
16
- in the Software without restriction, including without limitation the rights
17
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
- copies of the Software, and to permit persons to whom the Software is
19
- furnished to do so, subject to the following conditions:
20
-
21
- The above copyright notice and this permission notice shall be included in all
22
- copies or substantial portions of the Software.
23
-
24
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
- SOFTWARE.
31
- License-File: LICENSE
32
- Keywords: asyncio,logging,mermaid,mermaid-trace,sequence-diagram,trace,visualization
33
- Classifier: Development Status :: 4 - Beta
34
- Classifier: Intended Audience :: Developers
35
- Classifier: License :: OSI Approved :: MIT License
36
- Classifier: Operating System :: OS Independent
37
- Classifier: Programming Language :: Python
38
- Classifier: Programming Language :: Python :: 3.10
39
- Classifier: Programming Language :: Python :: 3.11
40
- Classifier: Programming Language :: Python :: 3.12
41
- Classifier: Programming Language :: Python :: 3.13
42
- Classifier: Programming Language :: Python :: Implementation :: CPython
43
- Classifier: Programming Language :: Python :: Implementation :: PyPy
44
- Classifier: Topic :: Software Development :: Debuggers
45
- Classifier: Topic :: System :: Logging
46
- Requires-Python: >=3.10
47
- Requires-Dist: typing-extensions>=4.0.0
48
- Requires-Dist: watchdog>=2.0.0
49
- Provides-Extra: all
50
- Requires-Dist: fastapi>=0.100.0; extra == 'all'
51
- Provides-Extra: dev
52
- Requires-Dist: fastapi>=0.100.0; extra == 'dev'
53
- Requires-Dist: httpx; extra == 'dev'
54
- Requires-Dist: mypy; extra == 'dev'
55
- Requires-Dist: pytest; extra == 'dev'
56
- Requires-Dist: pytest-asyncio; extra == 'dev'
57
- Requires-Dist: pytest-cov; extra == 'dev'
58
- Requires-Dist: ruff; extra == 'dev'
59
- Provides-Extra: fastapi
60
- Requires-Dist: fastapi>=0.100.0; extra == 'fastapi'
61
- Description-Content-Type: text/markdown
62
-
63
- # MermaidTrace: The Python Logger That Draws Diagrams
64
-
65
- 🌐 **Language**: [English](README.md) | [中文](README_CN.md)
66
-
67
- [![PyPI version](https://img.shields.io/pypi/v/mermaid-trace.svg?style=flat-square&color=blue)](https://pypi.org/project/mermaid-trace/)
68
- [![Python Versions](https://img.shields.io/pypi/pyversions/mermaid-trace.svg?style=flat-square&color=blue)](https://pypi.org/project/mermaid-trace/)
69
- [![License](https://img.shields.io/github/license/xt765/mermaid-trace?style=flat-square)](LICENSE)
70
- [![CI Status](https://img.shields.io/github/actions/workflow/status/xt765/mermaid-trace/ci.yml?style=flat-square&label=CI)](https://github.com/xt765/mermaid-trace/actions/workflows/ci.yml)
71
- [![Codecov](https://img.shields.io/codecov/c/github/xt765/mermaid-trace?style=flat-square&logo=codecov)](https://codecov.io/gh/xt765/mermaid-trace)
72
-
73
- ---
74
-
75
- ## 📋 Overview
76
-
77
- **Stop reading logs. Start watching them.**
78
-
79
- MermaidTrace is a specialized logging tool that automatically generates [Mermaid JS](https://mermaid.js.org/) sequence diagrams from your code execution. It's perfect for visualizing complex business logic, microservice interactions, or asynchronous flows.
80
-
81
- ---
82
-
83
- ## 📚 Documentation
84
-
85
- ### Core Documentation
86
-
87
- [User Guide](docs/en/USER_GUIDE.md) · [API Reference](docs/en/API.md) · [Contributing Guidelines](docs/en/CONTRIBUTING.md) · [Changelog](docs/en/CHANGELOG.md) · [License](docs/en/LICENSE)
88
-
89
- ### Code Comment Documents (Chinese)
90
-
91
- | Category | Links |
92
- | :--- | :--- |
93
- | **Core Modules** | [Context](docs/zh/code_comments/src/mermaid_trace/core/context.md) · [Decorators](docs/zh/code_comments/src/mermaid_trace/core/decorators.md) · [Events](docs/zh/code_comments/src/mermaid_trace/core/events.md) · [Formatter](docs/zh/code_comments/src/mermaid_trace/core/formatter.md) |
94
- | **Handlers** | [Async Handler](docs/zh/code_comments/src/mermaid_trace/handlers/async_handler.md) · [Mermaid Handler](docs/zh/code_comments/src/mermaid_trace/handlers/mermaid_handler.md) |
95
- | **Integrations** | [FastAPI](docs/zh/code_comments/src/mermaid_trace/integrations/fastapi.md) |
96
- | **Others** | [init](docs/zh/code_comments/src/mermaid_trace/__init__.md) · [CLI](docs/zh/code_comments/src/mermaid_trace/cli.md) |
97
-
98
- ---
99
-
100
- ## ✨ Key Features
101
-
102
- - **Decorator-Driven**: Just add `@trace` or `@trace_interaction` to your functions.
103
- - **Auto-Instrumentation**: Use `@trace_class` to trace a whole class at once.
104
- - **Third-Party Patching**: Use `patch_object` to trace calls inside external libraries.
105
- - **Auto-Diagramming**: Generates `.mmd` files that can be viewed in VS Code, GitHub, or Mermaid Live Editor.
106
- - **Async Support**: Works seamlessly with `asyncio` coroutines.
107
- - **Context Inference**: Automatically tracks nested calls and infers `source` participants using `contextvars`.
108
- - **Intelligent Collapsing**: Prevents diagram explosion by collapsing repetitive high-frequency calls and identifying recurring patterns (e.g., loops).
109
- - **Detailed Exceptions**: Captures full stack traces for errors, displayed in interactive notes.
110
- - **Simplified Objects**: Automatically cleans up memory addresses (e.g., `<__main__.Obj at 0x...>` -> `<Obj>`) and **groups consecutive identical items** in lists/tuples (e.g., `[<Obj> x 5]`) for cleaner diagrams.
111
- - **Log Rotation**: Supports `RotatingMermaidFileHandler` for handling long-running systems by splitting logs based on size or time.
112
- - **FastAPI Integration**: Includes middleware for zero-config HTTP request tracing, supporting distributed tracing via `X-Trace-ID` and `X-Source` headers.
113
- - **CLI Tool**: Built-in viewer with live-reload to preview diagrams in your browser.
114
-
115
- ---
116
-
117
- ## 🚀 Quick Start
118
-
119
- ### Installation
120
-
121
- ```bash
122
- pip install mermaid-trace
123
- ```
124
-
125
- ### Basic Usage
126
-
127
- ```python
128
- from mermaid_trace import trace, configure_flow
129
- import time
130
-
131
- # 1. Configure output
132
- # Recommendation: Store diagrams in a dedicated directory (e.g., mermaid_diagrams/)
133
- configure_flow("mermaid_diagrams/my_flow.mmd", async_mode=True)
134
-
135
- # 2. Add decorators
136
- @trace(source="Client", target="PaymentService", action="Process Payment")
137
- def process_payment(amount):
138
- if check_balance(amount):
139
- return "Success"
140
- return "Failed"
141
-
142
- @trace(source="PaymentService", target="Database", action="Check Balance")
143
- def check_balance(amount):
144
- return True
145
-
146
- # 3. Run your code
147
- process_payment(100)
148
- ```
149
-
150
- ### Configuration
151
-
152
- You can configure global settings via `configure_flow` or environment variables to control performance and behavior.
153
-
154
- ```python
155
- configure_flow(
156
- "flow.mmd",
157
- overwrite=True, # Overwrite the file on each restart (default: True)
158
- level=logging.DEBUG,
159
- queue_size=5000, # Increase buffer for high-throughput
160
- config_overrides={
161
- "capture_args": False, # Disable arg capturing for max performance
162
- "max_string_length": 100 # Increase string truncation limit
163
- }
164
- )
165
- ```
166
-
167
- **Environment Variables:**
168
- - `MERMAID_TRACE_CAPTURE_ARGS` (true/false)
169
- - `MERMAID_TRACE_MAX_STRING_LENGTH` (int)
170
- - `MERMAID_TRACE_MAX_ARG_DEPTH` (int)
171
- - `MERMAID_TRACE_QUEUE_SIZE` (int)
172
-
173
- ### Nested Calls (Context Inference)
174
-
175
- You don't need to specify `source` every time. MermaidTrace infers it from the current context.
176
-
177
- ```python
178
- @trace(source="Client", target="API")
179
- def main():
180
- # Inside here, current participant is "API"
181
- service_call()
182
-
183
- @trace(target="Service") # source inferred as "API"
184
- def service_call():
185
- pass
186
- ```
187
-
188
- ### FastAPI Integration
189
-
190
- ```python
191
- from fastapi import FastAPI
192
- from mermaid_trace.integrations.fastapi import MermaidTraceMiddleware
193
-
194
- app = FastAPI()
195
- app.add_middleware(MermaidTraceMiddleware, app_name="MyAPI")
196
-
197
- @app.get("/")
198
- async def root():
199
- return {"message": "Hello World"}
200
- ```
201
-
202
- ### CLI Viewer
203
-
204
- Visualize your generated `.mmd` files instantly:
205
-
206
- ```bash
207
- mermaid-trace serve my_flow.mmd
208
- ```
209
-
210
- ### Examples
211
-
212
- Check out the [examples/](examples/) directory for a complete set of demos covering all features:
213
- - **[Basic Usage](examples/01_basic_usage.py)**: Decorators and class methods.
214
- - **[Advanced Instrumentation](examples/02_advanced_instrumentation.py)**: `@trace_class` and `patch_object` for third-party libraries.
215
- - **[Async & Concurrency](examples/03_async_concurrency.py)**: Tracing `asyncio` and concurrent tasks.
216
- - **[Error Handling](examples/04_error_handling.py)**: Stack trace capture and error rendering.
217
- - **[Intelligent Collapsing](examples/05_intelligent_collapsing.py)**: Keeping diagrams clean in loops.
218
- - **[FastAPI Integration](examples/06_fastapi_integration.py)**: Middleware for web apps.
219
- - **[Full Stack App](examples/07_full_stack_app.py)**: Comprehensive example with FastAPI, SQLAlchemy, and Pydantic.
220
- - **[Log Rotation](examples/08-log-rotation.py)**: Handling long-running processes with file rotation.
221
-
222
- ---
223
-
224
- ## 🤝 Contributing
225
-
226
- We welcome contributions! Please see [CONTRIBUTING.md](docs/en/CONTRIBUTING.md) for details.
227
-
228
- ---
229
-
230
- ## 📄 License
231
-
232
- MIT