smartdump 0.1.0__tar.gz

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.
@@ -0,0 +1,172 @@
1
+ Metadata-Version: 2.4
2
+ Name: smartdump
3
+ Version: 0.1.0
4
+ Summary: Real-time variable dumper for FastAPI — inspired by Laradumps
5
+ License: MIT
6
+ Keywords: fastapi,debug,dump,developer-tools
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: fastapi>=0.110.0
10
+ Requires-Dist: uvicorn[standard]>=0.29.0
11
+ Requires-Dist: requests>=2.31.0
12
+ Provides-Extra: dev
13
+ Requires-Dist: httpx>=0.27.0; extra == "dev"
14
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
15
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
16
+
17
+ # ⚡ SmartDebugger
18
+
19
+ Real-time variable dump viewer for FastAPI — inspired by [Laradumps](https://laradumps.dev).
20
+
21
+ Call `sd(variable)` anywhere in your FastAPI app and see it appear instantly in the browser.
22
+
23
+ ---
24
+
25
+ ## Quick start
26
+
27
+ ### 1. Install dependencies
28
+
29
+ ```bash
30
+ pip install fastapi uvicorn requests
31
+ ```
32
+
33
+ ### 2. Start the debugger server
34
+
35
+ ```bash
36
+ python run.py
37
+ ```
38
+
39
+ Open **http://localhost:8765** in your browser.
40
+
41
+ ### 3. Use `sd()` in your FastAPI app
42
+
43
+ ```python
44
+ from client import ds
45
+
46
+ @app.get("/users/{user_id}")
47
+ async def get_user(user_id: int):
48
+ user = db.get_user(user_id)
49
+ sd(user, label="user from DB") # dumps to the UI, non-blocking
50
+ return user
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Project structure
56
+
57
+ ```
58
+ smart_debugger/
59
+ ├── client/ # Python client library (the sd() function)
60
+ │ ├── __init__.py
61
+ │ └── ds.py
62
+ ├── server/ # FastAPI dump server
63
+ │ ├── __init__.py
64
+ │ ├── app.py # Routes: POST /dump, GET /dumps, WS /ws
65
+ │ └── manager.py # WebSocket connection manager
66
+ ├── static/ # Web UI (vanilla JS, no build step)
67
+ │ ├── index.html
68
+ │ ├── styles.css
69
+ │ └── app.js
70
+ ├── example/
71
+ │ └── app.py # Demo FastAPI app
72
+ ├── run.py # Server launcher
73
+ └── pyproject.toml
74
+ ```
75
+
76
+ ---
77
+
78
+ ## `sd()` reference
79
+
80
+ ```python
81
+ sd(data, label=None, level="info")
82
+ ```
83
+
84
+ | Parameter | Type | Default | Description |
85
+ |-----------|------|---------|-------------|
86
+ | `data` | any | — | Any Python value to inspect |
87
+ | `label` | str | `None` | Human-readable name shown in the UI |
88
+ | `level` | str | `"info"`| One of `info`, `debug`, `warning`, `error` |
89
+
90
+ **Returns** `data` unchanged, so you can inline the call:
91
+
92
+ ```python
93
+ return sd(result, "final result")
94
+ ```
95
+
96
+ ### Configuration
97
+
98
+ ```python
99
+ from client import configure
100
+
101
+ configure(
102
+ server_url="http://localhost:8765", # default
103
+ timeout=1.0, # seconds — kept short
104
+ enabled=True, # set False in production
105
+ )
106
+ ```
107
+
108
+ ---
109
+
110
+ ## UI features
111
+
112
+ - Real-time WebSocket feed
113
+ - Collapsible JSON tree viewer (auto-collapses at depth 3)
114
+ - Level badges: `info` / `debug` / `warning` / `error`
115
+ - Filter by level and free-text search (label, file, value)
116
+ - Sort newest / oldest
117
+ - Copy-to-clipboard button
118
+ - File + line number + function name for every dump
119
+ - Dark mode by default
120
+
121
+ ---
122
+
123
+ ## Example routes (demo app)
124
+
125
+ ```bash
126
+ # Start server
127
+ python run.py
128
+
129
+ # Start demo app in another terminal
130
+ uvicorn example.app:app --port 8000 --reload
131
+
132
+ # Hit the routes and watch the UI
133
+ curl http://localhost:8000/
134
+ curl http://localhost:8000/users/42
135
+ curl http://localhost:8000/error
136
+ curl -X POST http://localhost:8000/items \
137
+ -H "Content-Type: application/json" \
138
+ -d '{"name": "widget", "price": 9.99}'
139
+ curl http://localhost:8000/products
140
+ ```
141
+
142
+ ---
143
+
144
+ ## How it works
145
+
146
+ ```
147
+ FastAPI app SmartDebugger server Browser
148
+ │ │ │
149
+ │ sd(data) │ │
150
+ │─── POST /dump ────────>│ │
151
+ │ (daemon thread) │── WS broadcast ─────>│
152
+ │ │ │ (live update)
153
+ │ (request continues) │ │
154
+ ```
155
+
156
+ 1. `sd()` captures the caller's file/line/function via `inspect`.
157
+ 2. It serialises the value and fires a POST request on a **daemon thread** — completely non-blocking.
158
+ 3. The server stores the dump in a `deque(maxlen=200)` and broadcasts it to all connected WebSocket clients.
159
+ 4. The browser receives the payload and renders it as a collapsible JSON tree.
160
+
161
+ If the server is not running, `sd()` silently does nothing — it never crashes your app.
162
+
163
+ ---
164
+
165
+ ## Disabling in production
166
+
167
+ ```python
168
+ import os
169
+ from client import configure
170
+
171
+ configure(enabled=os.getenv("DEBUG", "false").lower() == "true")
172
+ ```
@@ -0,0 +1,156 @@
1
+ # ⚡ SmartDebugger
2
+
3
+ Real-time variable dump viewer for FastAPI — inspired by [Laradumps](https://laradumps.dev).
4
+
5
+ Call `sd(variable)` anywhere in your FastAPI app and see it appear instantly in the browser.
6
+
7
+ ---
8
+
9
+ ## Quick start
10
+
11
+ ### 1. Install dependencies
12
+
13
+ ```bash
14
+ pip install fastapi uvicorn requests
15
+ ```
16
+
17
+ ### 2. Start the debugger server
18
+
19
+ ```bash
20
+ python run.py
21
+ ```
22
+
23
+ Open **http://localhost:8765** in your browser.
24
+
25
+ ### 3. Use `sd()` in your FastAPI app
26
+
27
+ ```python
28
+ from client import ds
29
+
30
+ @app.get("/users/{user_id}")
31
+ async def get_user(user_id: int):
32
+ user = db.get_user(user_id)
33
+ sd(user, label="user from DB") # dumps to the UI, non-blocking
34
+ return user
35
+ ```
36
+
37
+ ---
38
+
39
+ ## Project structure
40
+
41
+ ```
42
+ smart_debugger/
43
+ ├── client/ # Python client library (the sd() function)
44
+ │ ├── __init__.py
45
+ │ └── ds.py
46
+ ├── server/ # FastAPI dump server
47
+ │ ├── __init__.py
48
+ │ ├── app.py # Routes: POST /dump, GET /dumps, WS /ws
49
+ │ └── manager.py # WebSocket connection manager
50
+ ├── static/ # Web UI (vanilla JS, no build step)
51
+ │ ├── index.html
52
+ │ ├── styles.css
53
+ │ └── app.js
54
+ ├── example/
55
+ │ └── app.py # Demo FastAPI app
56
+ ├── run.py # Server launcher
57
+ └── pyproject.toml
58
+ ```
59
+
60
+ ---
61
+
62
+ ## `sd()` reference
63
+
64
+ ```python
65
+ sd(data, label=None, level="info")
66
+ ```
67
+
68
+ | Parameter | Type | Default | Description |
69
+ |-----------|------|---------|-------------|
70
+ | `data` | any | — | Any Python value to inspect |
71
+ | `label` | str | `None` | Human-readable name shown in the UI |
72
+ | `level` | str | `"info"`| One of `info`, `debug`, `warning`, `error` |
73
+
74
+ **Returns** `data` unchanged, so you can inline the call:
75
+
76
+ ```python
77
+ return sd(result, "final result")
78
+ ```
79
+
80
+ ### Configuration
81
+
82
+ ```python
83
+ from client import configure
84
+
85
+ configure(
86
+ server_url="http://localhost:8765", # default
87
+ timeout=1.0, # seconds — kept short
88
+ enabled=True, # set False in production
89
+ )
90
+ ```
91
+
92
+ ---
93
+
94
+ ## UI features
95
+
96
+ - Real-time WebSocket feed
97
+ - Collapsible JSON tree viewer (auto-collapses at depth 3)
98
+ - Level badges: `info` / `debug` / `warning` / `error`
99
+ - Filter by level and free-text search (label, file, value)
100
+ - Sort newest / oldest
101
+ - Copy-to-clipboard button
102
+ - File + line number + function name for every dump
103
+ - Dark mode by default
104
+
105
+ ---
106
+
107
+ ## Example routes (demo app)
108
+
109
+ ```bash
110
+ # Start server
111
+ python run.py
112
+
113
+ # Start demo app in another terminal
114
+ uvicorn example.app:app --port 8000 --reload
115
+
116
+ # Hit the routes and watch the UI
117
+ curl http://localhost:8000/
118
+ curl http://localhost:8000/users/42
119
+ curl http://localhost:8000/error
120
+ curl -X POST http://localhost:8000/items \
121
+ -H "Content-Type: application/json" \
122
+ -d '{"name": "widget", "price": 9.99}'
123
+ curl http://localhost:8000/products
124
+ ```
125
+
126
+ ---
127
+
128
+ ## How it works
129
+
130
+ ```
131
+ FastAPI app SmartDebugger server Browser
132
+ │ │ │
133
+ │ sd(data) │ │
134
+ │─── POST /dump ────────>│ │
135
+ │ (daemon thread) │── WS broadcast ─────>│
136
+ │ │ │ (live update)
137
+ │ (request continues) │ │
138
+ ```
139
+
140
+ 1. `sd()` captures the caller's file/line/function via `inspect`.
141
+ 2. It serialises the value and fires a POST request on a **daemon thread** — completely non-blocking.
142
+ 3. The server stores the dump in a `deque(maxlen=200)` and broadcasts it to all connected WebSocket clients.
143
+ 4. The browser receives the payload and renders it as a collapsible JSON tree.
144
+
145
+ If the server is not running, `sd()` silently does nothing — it never crashes your app.
146
+
147
+ ---
148
+
149
+ ## Disabling in production
150
+
151
+ ```python
152
+ import os
153
+ from client import configure
154
+
155
+ configure(enabled=os.getenv("DEBUG", "false").lower() == "true")
156
+ ```
@@ -0,0 +1,3 @@
1
+ from .sd import sd, configure
2
+
3
+ __all__ = ["sd", "configure"]
@@ -0,0 +1,156 @@
1
+ """
2
+ SmartDebugger client — ds() function.
3
+
4
+ Usage:
5
+ from client import sd
6
+
7
+ sd(my_var)
8
+ sd(my_var, label="user object")
9
+ sd(my_var, label="error", level="error")
10
+
11
+ Works in both sync and async FastAPI routes. Non-blocking.
12
+ """
13
+ import inspect
14
+ import json
15
+ import threading
16
+ import uuid
17
+ from datetime import datetime, timezone
18
+ from typing import Any, Optional
19
+ import requests
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # Configuration
23
+ # ---------------------------------------------------------------------------
24
+
25
+ _config = {
26
+ "server_url": "http://localhost:8765",
27
+ "timeout": 1.0, # seconds — kept short so it never blocks
28
+ "enabled": True,
29
+ }
30
+
31
+ def configure(
32
+ server_url: str = "http://localhost:8765",
33
+ timeout: float = 1.0,
34
+ enabled: bool = True,
35
+ ) -> None:
36
+ """Override default ds() configuration."""
37
+ _config["server_url"] = server_url.rstrip("/")
38
+ _config["timeout"] = timeout
39
+ _config["enabled"] = enabled
40
+
41
+ # ---------------------------------------------------------------------------
42
+ # Serialisation helpers
43
+ # ---------------------------------------------------------------------------
44
+
45
+ def _safe_serialize(obj: Any) -> Any:
46
+ """
47
+ Recursively convert an arbitrary Python object into something JSON-safe.
48
+ Falls back to repr() for unrecognised types.
49
+ """
50
+ if obj is None or isinstance(obj, (bool, int, float, str)):
51
+ return obj
52
+
53
+ if isinstance(obj, dict):
54
+ return {str(k): _safe_serialize(v) for k, v in obj.items()}
55
+
56
+ if isinstance(obj, (list, tuple, set, frozenset)):
57
+ return [_safe_serialize(item) for item in obj]
58
+
59
+ if isinstance(obj, bytes):
60
+ try:
61
+ return obj.decode("utf-8")
62
+ except UnicodeDecodeError:
63
+ return obj.hex()
64
+
65
+ if isinstance(obj, datetime):
66
+ return obj.isoformat()
67
+
68
+ if isinstance(obj, BaseException):
69
+ return {
70
+ "__exception__": type(obj).__name__,
71
+ "message": str(obj),
72
+ "args": [_safe_serialize(a) for a in obj.args],
73
+ }
74
+
75
+ # Dataclasses, Pydantic models, plain objects
76
+ if hasattr(obj, "__dict__"):
77
+ return {
78
+ "__type__": type(obj).__qualname__,
79
+ **{k: _safe_serialize(v) for k, v in obj.__dict__.items()
80
+ if not k.startswith("_")},
81
+ }
82
+
83
+ # Anything else → safe string
84
+ return repr(obj)
85
+
86
+
87
+ def _build_payload(
88
+ data: Any,
89
+ label: Optional[str],
90
+ level: str,
91
+ caller_frame,
92
+ ) -> dict:
93
+ filename = caller_frame.f_code.co_filename
94
+ return {
95
+ "id": str(uuid.uuid4()),
96
+ "label": label,
97
+ "level": level,
98
+ "type": type(data).__name__,
99
+ "data": _safe_serialize(data),
100
+ "meta": {
101
+ "file": filename,
102
+ "line": caller_frame.f_lineno,
103
+ "function": caller_frame.f_code.co_name,
104
+ },
105
+ "timestamp": datetime.now(timezone.utc).isoformat(),
106
+ }
107
+
108
+
109
+ # ---------------------------------------------------------------------------
110
+ # Background sender (daemon thread → never blocks the request lifecycle)
111
+ # ---------------------------------------------------------------------------
112
+
113
+ def _send_to_server(payload: dict) -> None:
114
+ try:
115
+ requests.post(
116
+ f"{_config['server_url']}/dump",
117
+ json=payload,
118
+ timeout=_config["timeout"],
119
+ )
120
+ except Exception:
121
+ # Silently ignore — server may not be running; never crash the app
122
+ pass
123
+
124
+
125
+ # ---------------------------------------------------------------------------
126
+ # Public API
127
+ # ---------------------------------------------------------------------------
128
+
129
+ def sd(
130
+ data: Any,
131
+ label: Optional[str] = None,
132
+ level: str = "info",
133
+ ) -> Any:
134
+ """
135
+ SmartDebugger — send *data* to the SmartDebugger viewer.
136
+
137
+ Args:
138
+ data: Any Python value to inspect.
139
+ label: Optional human-readable label shown in the UI.
140
+ level: One of "info" | "debug" | "warning" | "error".
141
+
142
+ Returns:
143
+ *data* unchanged, so you can inline the call:
144
+ ``return sd(result, "final result")``
145
+ """
146
+ if not _config["enabled"]:
147
+ return data
148
+
149
+ # Capture the caller's frame *before* spawning a thread
150
+ caller_frame = inspect.currentframe().f_back
151
+ payload = _build_payload(data, label, level, caller_frame)
152
+
153
+ # Fire-and-forget on a daemon thread — works in sync and async contexts
154
+ threading.Thread(target=_send_to_server, args=(payload,), daemon=True).start()
155
+
156
+ return data
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "smartdump"
7
+ version = "0.1.0"
8
+ description = "Real-time variable dumper for FastAPI — inspired by Laradumps"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ keywords = ["fastapi", "debug", "dump", "developer-tools"]
13
+
14
+ dependencies = [
15
+ "fastapi>=0.110.0",
16
+ "uvicorn[standard]>=0.29.0",
17
+ "requests>=2.31.0",
18
+ ]
19
+
20
+ [project.optional-dependencies]
21
+ dev = [
22
+ "httpx>=0.27.0",
23
+ "pytest>=8.0.0",
24
+ "pytest-asyncio>=0.23.0",
25
+ ]
26
+
27
+ [project.scripts]
28
+ smart-debugger = "run:main"
29
+ smartdebug = "run:cli"
30
+
31
+ [tool.setuptools.packages.find]
32
+ include = ["client*", "server*"]
33
+ exclude = ["example*", "tests*"]
File without changes
@@ -0,0 +1,95 @@
1
+ """
2
+ SmartDebugger server.
3
+
4
+ Endpoints:
5
+ POST /dump — receive a dump from the client library
6
+ GET /dumps — return all stored dumps (newest first)
7
+ GET /dumps/clear — clear all stored dumps
8
+ WS /ws — real-time WebSocket feed for the UI
9
+ GET / — serve the web UI
10
+ GET /health — health check
11
+ """
12
+
13
+ import json
14
+ from collections import deque
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ from fastapi import FastAPI, WebSocket, WebSocketDisconnect
19
+ from fastapi.middleware.cors import CORSMiddleware
20
+ from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
21
+ from fastapi.staticfiles import StaticFiles
22
+
23
+ from .manager import ConnectionManager
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # App setup
27
+ # ---------------------------------------------------------------------------
28
+
29
+ app = FastAPI(title="SmartDebugger Server", docs_url=None, redoc_url=None)
30
+
31
+ app.add_middleware(
32
+ CORSMiddleware,
33
+ allow_origins=["*"],
34
+ allow_methods=["*"],
35
+ allow_headers=["*"],
36
+ )
37
+
38
+ # In-memory store — keep the 200 most recent dumps
39
+ MAX_DUMPS = 200
40
+ _dumps: deque[dict] = deque(maxlen=MAX_DUMPS)
41
+
42
+ manager = ConnectionManager()
43
+
44
+ STATIC_DIR = Path(__file__).parent.parent / "static"
45
+
46
+
47
+ # ---------------------------------------------------------------------------
48
+ # Routes
49
+ # ---------------------------------------------------------------------------
50
+
51
+ @app.get("/health")
52
+ async def health() -> dict:
53
+ return {"status": "ok", "clients": manager.client_count, "dumps": len(_dumps)}
54
+
55
+
56
+ @app.post("/dump")
57
+ async def receive_dump(payload: dict) -> dict:
58
+ """Accept a dump from the client library and broadcast it to all UI tabs."""
59
+ _dumps.appendleft(payload)
60
+ await manager.broadcast({"event": "dump", "payload": payload})
61
+ return {"ok": True}
62
+
63
+
64
+ @app.get("/dumps")
65
+ async def get_dumps() -> list:
66
+ """Return all stored dumps, newest first."""
67
+ return list(_dumps)
68
+
69
+
70
+ @app.delete("/dumps")
71
+ async def clear_dumps() -> dict:
72
+ """Clear all stored dumps and notify connected clients."""
73
+ _dumps.clear()
74
+ await manager.broadcast({"event": "clear"})
75
+ return {"ok": True}
76
+
77
+
78
+ @app.websocket("/ws")
79
+ async def websocket_endpoint(ws: WebSocket) -> None:
80
+ await manager.connect(ws)
81
+ try:
82
+ while True:
83
+ # Keep connection alive; client doesn't need to send anything
84
+ await ws.receive_text()
85
+ except WebSocketDisconnect:
86
+ manager.disconnect(ws)
87
+
88
+
89
+ @app.get("/")
90
+ async def serve_ui() -> FileResponse:
91
+ return FileResponse(STATIC_DIR / "index.html")
92
+
93
+
94
+ # Mount static files (JS, CSS) under /static
95
+ app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
@@ -0,0 +1,43 @@
1
+ """
2
+ WebSocket connection manager.
3
+ Keeps track of every connected browser tab and broadcasts payloads to all.
4
+ """
5
+
6
+ import asyncio
7
+ import json
8
+ from typing import Set
9
+
10
+ from fastapi import WebSocket
11
+
12
+
13
+ class ConnectionManager:
14
+ def __init__(self) -> None:
15
+ self._clients: Set[WebSocket] = set()
16
+
17
+ async def connect(self, ws: WebSocket) -> None:
18
+ await ws.accept()
19
+ self._clients.add(ws)
20
+
21
+ def disconnect(self, ws: WebSocket) -> None:
22
+ self._clients.discard(ws)
23
+
24
+ async def broadcast(self, payload: dict) -> None:
25
+ """Send *payload* to every connected client, removing dead connections."""
26
+ if not self._clients:
27
+ return
28
+
29
+ message = json.dumps(payload)
30
+ dead: list[WebSocket] = []
31
+
32
+ for client in list(self._clients):
33
+ try:
34
+ await client.send_text(message)
35
+ except Exception:
36
+ dead.append(client)
37
+
38
+ for ws in dead:
39
+ self._clients.discard(ws)
40
+
41
+ @property
42
+ def client_count(self) -> int:
43
+ return len(self._clients)
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,172 @@
1
+ Metadata-Version: 2.4
2
+ Name: smartdump
3
+ Version: 0.1.0
4
+ Summary: Real-time variable dumper for FastAPI — inspired by Laradumps
5
+ License: MIT
6
+ Keywords: fastapi,debug,dump,developer-tools
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: fastapi>=0.110.0
10
+ Requires-Dist: uvicorn[standard]>=0.29.0
11
+ Requires-Dist: requests>=2.31.0
12
+ Provides-Extra: dev
13
+ Requires-Dist: httpx>=0.27.0; extra == "dev"
14
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
15
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
16
+
17
+ # ⚡ SmartDebugger
18
+
19
+ Real-time variable dump viewer for FastAPI — inspired by [Laradumps](https://laradumps.dev).
20
+
21
+ Call `sd(variable)` anywhere in your FastAPI app and see it appear instantly in the browser.
22
+
23
+ ---
24
+
25
+ ## Quick start
26
+
27
+ ### 1. Install dependencies
28
+
29
+ ```bash
30
+ pip install fastapi uvicorn requests
31
+ ```
32
+
33
+ ### 2. Start the debugger server
34
+
35
+ ```bash
36
+ python run.py
37
+ ```
38
+
39
+ Open **http://localhost:8765** in your browser.
40
+
41
+ ### 3. Use `sd()` in your FastAPI app
42
+
43
+ ```python
44
+ from client import ds
45
+
46
+ @app.get("/users/{user_id}")
47
+ async def get_user(user_id: int):
48
+ user = db.get_user(user_id)
49
+ sd(user, label="user from DB") # dumps to the UI, non-blocking
50
+ return user
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Project structure
56
+
57
+ ```
58
+ smart_debugger/
59
+ ├── client/ # Python client library (the sd() function)
60
+ │ ├── __init__.py
61
+ │ └── ds.py
62
+ ├── server/ # FastAPI dump server
63
+ │ ├── __init__.py
64
+ │ ├── app.py # Routes: POST /dump, GET /dumps, WS /ws
65
+ │ └── manager.py # WebSocket connection manager
66
+ ├── static/ # Web UI (vanilla JS, no build step)
67
+ │ ├── index.html
68
+ │ ├── styles.css
69
+ │ └── app.js
70
+ ├── example/
71
+ │ └── app.py # Demo FastAPI app
72
+ ├── run.py # Server launcher
73
+ └── pyproject.toml
74
+ ```
75
+
76
+ ---
77
+
78
+ ## `sd()` reference
79
+
80
+ ```python
81
+ sd(data, label=None, level="info")
82
+ ```
83
+
84
+ | Parameter | Type | Default | Description |
85
+ |-----------|------|---------|-------------|
86
+ | `data` | any | — | Any Python value to inspect |
87
+ | `label` | str | `None` | Human-readable name shown in the UI |
88
+ | `level` | str | `"info"`| One of `info`, `debug`, `warning`, `error` |
89
+
90
+ **Returns** `data` unchanged, so you can inline the call:
91
+
92
+ ```python
93
+ return sd(result, "final result")
94
+ ```
95
+
96
+ ### Configuration
97
+
98
+ ```python
99
+ from client import configure
100
+
101
+ configure(
102
+ server_url="http://localhost:8765", # default
103
+ timeout=1.0, # seconds — kept short
104
+ enabled=True, # set False in production
105
+ )
106
+ ```
107
+
108
+ ---
109
+
110
+ ## UI features
111
+
112
+ - Real-time WebSocket feed
113
+ - Collapsible JSON tree viewer (auto-collapses at depth 3)
114
+ - Level badges: `info` / `debug` / `warning` / `error`
115
+ - Filter by level and free-text search (label, file, value)
116
+ - Sort newest / oldest
117
+ - Copy-to-clipboard button
118
+ - File + line number + function name for every dump
119
+ - Dark mode by default
120
+
121
+ ---
122
+
123
+ ## Example routes (demo app)
124
+
125
+ ```bash
126
+ # Start server
127
+ python run.py
128
+
129
+ # Start demo app in another terminal
130
+ uvicorn example.app:app --port 8000 --reload
131
+
132
+ # Hit the routes and watch the UI
133
+ curl http://localhost:8000/
134
+ curl http://localhost:8000/users/42
135
+ curl http://localhost:8000/error
136
+ curl -X POST http://localhost:8000/items \
137
+ -H "Content-Type: application/json" \
138
+ -d '{"name": "widget", "price": 9.99}'
139
+ curl http://localhost:8000/products
140
+ ```
141
+
142
+ ---
143
+
144
+ ## How it works
145
+
146
+ ```
147
+ FastAPI app SmartDebugger server Browser
148
+ │ │ │
149
+ │ sd(data) │ │
150
+ │─── POST /dump ────────>│ │
151
+ │ (daemon thread) │── WS broadcast ─────>│
152
+ │ │ │ (live update)
153
+ │ (request continues) │ │
154
+ ```
155
+
156
+ 1. `sd()` captures the caller's file/line/function via `inspect`.
157
+ 2. It serialises the value and fires a POST request on a **daemon thread** — completely non-blocking.
158
+ 3. The server stores the dump in a `deque(maxlen=200)` and broadcasts it to all connected WebSocket clients.
159
+ 4. The browser receives the payload and renders it as a collapsible JSON tree.
160
+
161
+ If the server is not running, `sd()` silently does nothing — it never crashes your app.
162
+
163
+ ---
164
+
165
+ ## Disabling in production
166
+
167
+ ```python
168
+ import os
169
+ from client import configure
170
+
171
+ configure(enabled=os.getenv("DEBUG", "false").lower() == "true")
172
+ ```
@@ -0,0 +1,13 @@
1
+ README.md
2
+ pyproject.toml
3
+ client/__init__.py
4
+ client/sd.py
5
+ server/__init__.py
6
+ server/app.py
7
+ server/manager.py
8
+ smartdump.egg-info/PKG-INFO
9
+ smartdump.egg-info/SOURCES.txt
10
+ smartdump.egg-info/dependency_links.txt
11
+ smartdump.egg-info/entry_points.txt
12
+ smartdump.egg-info/requires.txt
13
+ smartdump.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ smart-debugger = run:main
3
+ smartdebug = run:cli
@@ -0,0 +1,8 @@
1
+ fastapi>=0.110.0
2
+ uvicorn[standard]>=0.29.0
3
+ requests>=2.31.0
4
+
5
+ [dev]
6
+ httpx>=0.27.0
7
+ pytest>=8.0.0
8
+ pytest-asyncio>=0.23.0
@@ -0,0 +1,2 @@
1
+ client
2
+ server