py4web-debug-tools 1.3.0__tar.gz → 1.3.2__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.
- {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.2}/CHANGELOG.md +12 -0
- {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.2}/PKG-INFO +2 -1
- {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.2}/pyproject.toml +2 -1
- py4web_debug_tools-1.3.2/src/py4web_debug/__about__.py +1 -0
- {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.2}/src/py4web_debug/dumping.py +118 -22
- {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.2}/src/py4web_debug/internals.py +3 -4
- py4web_debug_tools-1.3.0/src/py4web_debug/__about__.py +0 -1
- {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.2}/.gitignore +0 -0
- {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.2}/README.md +0 -0
- {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.2}/src/py4web_debug/__init__.py +0 -0
- {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.2}/src/py4web_debug/_internals.py +0 -0
- {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.2}/src/py4web_debug/core.py +0 -0
- {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.2}/src/py4web_debug/debugbar.py +0 -0
- {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.2}/src/py4web_debug/env.py +0 -0
- {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.2}/src/py4web_debug/py.typed +0 -0
- {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.2}/src/py4web_debug/templates/debugbar.html +0 -0
- {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.2}/src/py4web_debug/templates/dumpdie.html +0 -0
- {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.2}/src/py4web_debug/templates/error_default.html +0 -0
- {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.2}/src/py4web_debug/templates/fancy_dumpdie.html +0 -0
- {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.2}/src/py4web_debug/types.py +0 -0
- {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.2}/src/py4web_debug/wsgi.py +0 -0
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
<!--next-version-placeholder-->
|
|
4
4
|
|
|
5
|
+
## v1.3.2 (2025-12-12)
|
|
6
|
+
|
|
7
|
+
### Fix
|
|
8
|
+
|
|
9
|
+
* Use existing debug-tools `dump` instead of py4web's `dumps`, hopefully to prevent circular import error ([`0d5afad`](https://github.com/trialandsuccess/py4web-debug-tools/commit/0d5afad2904834cf80d9252b89816b7ae360249a))
|
|
10
|
+
|
|
11
|
+
## v1.3.1 (2025-04-01)
|
|
12
|
+
|
|
13
|
+
### Fix
|
|
14
|
+
|
|
15
|
+
* **dbg:** Detect more variable names ([`a80556c`](https://github.com/trialandsuccess/py4web-debug-tools/commit/a80556c2d35f89a5c0f900518074b7800dfeb844))
|
|
16
|
+
|
|
5
17
|
## v1.3.0 (2025-04-01)
|
|
6
18
|
|
|
7
19
|
### Feature
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: py4web-debug-tools
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.2
|
|
4
4
|
Summary: Debug Tools for py4web
|
|
5
5
|
Project-URL: Documentation, https://github.com/trialandsuccess/py4web-debug-tools#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/trialandsuccess/py4web-debug-tools/issues
|
|
@@ -18,6 +18,7 @@ Requires-Python: >=3.10
|
|
|
18
18
|
Requires-Dist: configurable-json>=1.1
|
|
19
19
|
Requires-Dist: py4web
|
|
20
20
|
Requires-Dist: pydal
|
|
21
|
+
Requires-Dist: typing-extensions
|
|
21
22
|
Requires-Dist: yatl
|
|
22
23
|
Provides-Extra: dev
|
|
23
24
|
Requires-Dist: hatch; extra == 'dev'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.3.2"
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import ast
|
|
2
|
+
import inspect
|
|
2
3
|
import json
|
|
3
4
|
import sys
|
|
4
|
-
import traceback
|
|
5
5
|
import typing
|
|
6
|
+
from types import FrameType
|
|
6
7
|
|
|
7
8
|
import pydal.objects
|
|
8
9
|
from configurablejson import ConfigurableJsonEncoder, JSONRule
|
|
@@ -159,35 +160,130 @@ def dd(*data: typing.Any, fancy: bool = True, api: bool = False) -> None:
|
|
|
159
160
|
T = typing.TypeVar("T")
|
|
160
161
|
|
|
161
162
|
|
|
162
|
-
def
|
|
163
|
+
def _extract_dbg_variable_name(frame: FrameType) -> str:
|
|
164
|
+
# Read the full source of the caller file
|
|
165
|
+
with open(frame.f_code.co_filename, "r", encoding="utf-8") as f:
|
|
166
|
+
source_code = f.readlines()
|
|
167
|
+
|
|
168
|
+
# Extract the relevant lines (handle multiline statements)
|
|
169
|
+
full_source = ""
|
|
170
|
+
line_number = frame.f_lineno - 1
|
|
171
|
+
while line_number < len(source_code):
|
|
172
|
+
full_source += source_code[line_number].strip()
|
|
173
|
+
if full_source.count("(") == full_source.count(")"): # Ensure all parentheses are closed
|
|
174
|
+
break
|
|
175
|
+
line_number += 1
|
|
176
|
+
|
|
177
|
+
# Parse the AST
|
|
178
|
+
tree = ast.parse(full_source)
|
|
179
|
+
|
|
180
|
+
# Find the relevant call in the AST
|
|
181
|
+
var_name = ""
|
|
182
|
+
for node in ast.walk(tree):
|
|
183
|
+
if isinstance(node, ast.Expr) and isinstance(node.value, ast.Call):
|
|
184
|
+
call = node.value
|
|
185
|
+
while isinstance(call, ast.Call): # Handle nested function calls
|
|
186
|
+
if hasattr(call.func, "id") and call.func.id == "dbg":
|
|
187
|
+
var_name = ast.unparse(call.args[0])
|
|
188
|
+
break
|
|
189
|
+
elif isinstance(call.func, ast.Attribute) and call.func.attr == "dbg":
|
|
190
|
+
var_name = ast.unparse(call.args[0])
|
|
191
|
+
break
|
|
192
|
+
call = call.args[0] if call.args else None
|
|
193
|
+
elif isinstance(node, ast.Assign) and isinstance(node.value, ast.Call):
|
|
194
|
+
call = node.value
|
|
195
|
+
while isinstance(call, ast.Call): # Handle assignments with nested calls
|
|
196
|
+
if hasattr(call.func, "id") and call.func.id == "dbg":
|
|
197
|
+
var_name = ast.unparse(call.args[0])
|
|
198
|
+
break
|
|
199
|
+
elif isinstance(call.func, ast.Attribute) and call.func.attr == "dbg":
|
|
200
|
+
var_name = ast.unparse(call.args[0])
|
|
201
|
+
break
|
|
202
|
+
call = call.args[0] if call.args else None
|
|
203
|
+
elif isinstance(node, ast.Return) and isinstance(node.value, ast.Call):
|
|
204
|
+
call = node.value
|
|
205
|
+
while isinstance(call, ast.Call): # Handle return dbg(x)
|
|
206
|
+
if hasattr(call.func, "id") and call.func.id == "dbg":
|
|
207
|
+
var_name = ast.unparse(call.args[0])
|
|
208
|
+
break
|
|
209
|
+
elif isinstance(call.func, ast.Attribute) and call.func.attr == "dbg":
|
|
210
|
+
var_name = ast.unparse(call.args[0])
|
|
211
|
+
break
|
|
212
|
+
call = call.args[0] if call.args else None
|
|
213
|
+
elif isinstance(node, ast.keyword) and isinstance(node.value, ast.Call):
|
|
214
|
+
# Handle function keyword arguments like some_func(arg=dbg("arg"))
|
|
215
|
+
call = node.value
|
|
216
|
+
if hasattr(call.func, "id") and call.func.id == "dbg":
|
|
217
|
+
var_name = ast.unparse(call.args[0])
|
|
218
|
+
break
|
|
219
|
+
elif isinstance(call.func, ast.Attribute) and call.func.attr == "dbg":
|
|
220
|
+
var_name = ast.unparse(call.args[0])
|
|
221
|
+
break
|
|
222
|
+
|
|
223
|
+
return var_name
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def extract_dbg_variable_name(frame: FrameType) -> typing.Optional[str]:
|
|
227
|
+
"""
|
|
228
|
+
Extracts the variable name passed to dbg() from the source code.
|
|
229
|
+
Handles multiline statements, assignments, function arguments, and return values.
|
|
163
230
|
"""
|
|
164
|
-
|
|
231
|
+
try:
|
|
232
|
+
return _extract_dbg_variable_name(frame)
|
|
233
|
+
except Exception:
|
|
234
|
+
return None
|
|
235
|
+
|
|
165
236
|
|
|
166
|
-
|
|
167
|
-
Also returns the original value.
|
|
237
|
+
def dbg(value: T) -> T:
|
|
168
238
|
"""
|
|
169
|
-
|
|
239
|
+
Improved Rust-like dbg function that captures variable names even in assignments,
|
|
240
|
+
namespaced calls, and nested function calls.
|
|
241
|
+
Handles multiline calls, function arguments, and return statements.
|
|
170
242
|
|
|
171
|
-
|
|
172
|
-
file_name = frame.filename.split("/")[-1]
|
|
173
|
-
line_number = frame.lineno
|
|
243
|
+
Examples:
|
|
174
244
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
245
|
+
```
|
|
246
|
+
def handle(_):
|
|
247
|
+
...
|
|
178
248
|
|
|
179
|
-
|
|
180
|
-
tree = ast.parse(line)
|
|
249
|
+
def my_decorator(fn): return fn
|
|
181
250
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if isinstance(call, ast.Call) and call.func.id == "dbg":
|
|
186
|
-
var_name = ast.unparse(call.args[0]) # Extract the first argument as a string
|
|
251
|
+
@my_decorator
|
|
252
|
+
def some_func(arg):
|
|
253
|
+
return dbg("hello")
|
|
187
254
|
|
|
188
|
-
except Exception:
|
|
189
|
-
var_name = "?"
|
|
190
255
|
|
|
191
|
-
|
|
256
|
+
dbg('hi')
|
|
257
|
+
y = dbg('hi')
|
|
258
|
+
py4web_debug.dbg('hi')
|
|
259
|
+
z = py4web_debug.dbg('hi')
|
|
192
260
|
|
|
261
|
+
handle(dbg('hi'))
|
|
262
|
+
handle(py4web_debug.dbg('hi'))
|
|
263
|
+
|
|
264
|
+
some_long_expression = " 3 + 123"
|
|
265
|
+
|
|
266
|
+
handle(
|
|
267
|
+
dbg(
|
|
268
|
+
(
|
|
269
|
+
some_long_expression +
|
|
270
|
+
some_long_expression
|
|
271
|
+
)
|
|
272
|
+
)
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
some_func(arg=dbg("arg1"))
|
|
276
|
+
```
|
|
277
|
+
"""
|
|
278
|
+
value_type = type(value).__name__
|
|
279
|
+
|
|
280
|
+
# Get the caller's frame
|
|
281
|
+
frame = inspect.currentframe().f_back
|
|
282
|
+
frame_info = inspect.getframeinfo(frame)
|
|
283
|
+
file_name = frame_info.filename.split("/")[-1]
|
|
284
|
+
line_number = frame_info.lineno
|
|
285
|
+
|
|
286
|
+
var_name = extract_dbg_variable_name(frame) or "?"
|
|
287
|
+
|
|
288
|
+
print(f"[{file_name}:{line_number}] {value_type}({var_name}) = {value}", file=sys.stderr)
|
|
193
289
|
return value
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import functools
|
|
2
2
|
import http.client
|
|
3
|
-
import json
|
|
4
3
|
import logging
|
|
5
4
|
import os
|
|
6
5
|
import re
|
|
@@ -14,7 +13,6 @@ from py4web import HTTP, action, response
|
|
|
14
13
|
from py4web.core import (
|
|
15
14
|
REGEX_APPJSON,
|
|
16
15
|
Template,
|
|
17
|
-
dumps,
|
|
18
16
|
error_logger,
|
|
19
17
|
get_error_snapshot,
|
|
20
18
|
request,
|
|
@@ -22,6 +20,7 @@ from py4web.core import (
|
|
|
22
20
|
from typing_extensions import NotRequired
|
|
23
21
|
from yatl import XML
|
|
24
22
|
|
|
23
|
+
from .dumping import dump
|
|
25
24
|
from .types import ErrorSnapshot
|
|
26
25
|
|
|
27
26
|
|
|
@@ -79,7 +78,7 @@ def custom_error_page(
|
|
|
79
78
|
# if client accepts 'application/json' - return json
|
|
80
79
|
if re.search(REGEX_APPJSON, request.headers.get("accept", "")):
|
|
81
80
|
response.status = code
|
|
82
|
-
return
|
|
81
|
+
return dump(context)
|
|
83
82
|
# else - return html error-page
|
|
84
83
|
|
|
85
84
|
if renderer:
|
|
@@ -142,7 +141,7 @@ class patch_py4:
|
|
|
142
141
|
ret = func(*func_args, **func_kwargs)
|
|
143
142
|
if isinstance(ret, dict):
|
|
144
143
|
response.headers["Content-Type"] = "application/json"
|
|
145
|
-
ret =
|
|
144
|
+
ret = dump(ret)
|
|
146
145
|
return ret
|
|
147
146
|
except HTTP as http:
|
|
148
147
|
response.status = http.status
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.3.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.2}/src/py4web_debug/templates/debugbar.html
RENAMED
|
File without changes
|
{py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.2}/src/py4web_debug/templates/dumpdie.html
RENAMED
|
File without changes
|
{py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.2}/src/py4web_debug/templates/error_default.html
RENAMED
|
File without changes
|
{py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.2}/src/py4web_debug/templates/fancy_dumpdie.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|