mermaid-trace 0.4.1__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/__init__.py +53 -21
- mermaid_trace/cli.py +168 -86
- mermaid_trace/core/config.py +55 -0
- mermaid_trace/core/decorators.py +401 -196
- mermaid_trace/core/events.py +13 -143
- mermaid_trace/core/formatter.py +176 -18
- mermaid_trace/core/utils.py +96 -0
- mermaid_trace/handlers/async_handler.py +123 -47
- mermaid_trace/handlers/mermaid_handler.py +152 -86
- mermaid_trace/integrations/__init__.py +9 -0
- mermaid_trace/integrations/fastapi.py +101 -46
- mermaid_trace/integrations/langchain.py +312 -0
- mermaid_trace/server.py +406 -0
- mermaid_trace-0.6.0.post0.dist-info/METADATA +272 -0
- mermaid_trace-0.6.0.post0.dist-info/RECORD +21 -0
- mermaid_trace-0.4.1.dist-info/METADATA +0 -180
- mermaid_trace-0.4.1.dist-info/RECORD +0 -16
- {mermaid_trace-0.4.1.dist-info → mermaid_trace-0.6.0.post0.dist-info}/WHEEL +0 -0
- {mermaid_trace-0.4.1.dist-info → mermaid_trace-0.6.0.post0.dist-info}/entry_points.txt +0 -0
- {mermaid_trace-0.4.1.dist-info → mermaid_trace-0.6.0.post0.dist-info}/licenses/LICENSE +0 -0
mermaid_trace/server.py
ADDED
|
@@ -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
|
+
[](https://blog.csdn.net/Yunyi_Chi)
|
|
74
|
+
[](https://github.com/xt765/mermaid-trace)
|
|
75
|
+
[](https://gitee.com/xt765/mermaid-trace)
|
|
76
|
+
[](https://pypi.org/project/mermaid-trace/)
|
|
77
|
+
[](https://pypi.org/project/mermaid-trace/)
|
|
78
|
+
[](LICENSE)
|
|
79
|
+
[](https://github.com/xt765/mermaid-trace/actions/workflows/ci.yml)
|
|
80
|
+
[](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
|
+

|
|
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
|
+
[](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
|