py4web-debug-tools 1.3.0__tar.gz → 1.3.1__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.
Files changed (22) hide show
  1. {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.1}/CHANGELOG.md +6 -0
  2. {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.1}/PKG-INFO +2 -1
  3. py4web_debug_tools-1.3.1/example.py +29 -0
  4. {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.1}/pyproject.toml +2 -1
  5. py4web_debug_tools-1.3.1/src/py4web_debug/__about__.py +1 -0
  6. {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.1}/src/py4web_debug/dumping.py +118 -21
  7. py4web_debug_tools-1.3.0/src/py4web_debug/__about__.py +0 -1
  8. {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.1}/.gitignore +0 -0
  9. {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.1}/README.md +0 -0
  10. {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.1}/src/py4web_debug/__init__.py +0 -0
  11. {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.1}/src/py4web_debug/_internals.py +0 -0
  12. {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.1}/src/py4web_debug/core.py +0 -0
  13. {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.1}/src/py4web_debug/debugbar.py +0 -0
  14. {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.1}/src/py4web_debug/env.py +0 -0
  15. {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.1}/src/py4web_debug/internals.py +0 -0
  16. {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.1}/src/py4web_debug/py.typed +0 -0
  17. {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.1}/src/py4web_debug/templates/debugbar.html +0 -0
  18. {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.1}/src/py4web_debug/templates/dumpdie.html +0 -0
  19. {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.1}/src/py4web_debug/templates/error_default.html +0 -0
  20. {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.1}/src/py4web_debug/templates/fancy_dumpdie.html +0 -0
  21. {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.1}/src/py4web_debug/types.py +0 -0
  22. {py4web_debug_tools-1.3.0 → py4web_debug_tools-1.3.1}/src/py4web_debug/wsgi.py +0 -0
@@ -2,6 +2,12 @@
2
2
 
3
3
  <!--next-version-placeholder-->
4
4
 
5
+ ## v1.3.1 (2025-04-01)
6
+
7
+ ### Fix
8
+
9
+ * **dbg:** Detect more variable names ([`a80556c`](https://github.com/trialandsuccess/py4web-debug-tools/commit/a80556c2d35f89a5c0f900518074b7800dfeb844))
10
+
5
11
  ## v1.3.0 (2025-04-01)
6
12
 
7
13
  ### Feature
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py4web-debug-tools
3
- Version: 1.3.0
3
+ Version: 1.3.1
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,29 @@
1
+ import py4web_debug
2
+ from py4web_debug import dbg
3
+
4
+
5
+ def handle(_): ...
6
+
7
+
8
+ def my_decorator(fn):
9
+ return fn
10
+
11
+
12
+ @my_decorator
13
+ def some_func(arg):
14
+ return dbg("hello")
15
+
16
+
17
+ dbg("hi")
18
+ y = dbg("hi")
19
+ py4web_debug.dbg("hi")
20
+ z = py4web_debug.dbg("hi")
21
+
22
+ handle(dbg("hi"))
23
+ handle(py4web_debug.dbg("hi"))
24
+
25
+ some_long_expression = " 3 + 123"
26
+
27
+ handle(dbg((some_long_expression + some_long_expression)))
28
+
29
+ some_func(arg=dbg("arg1"))
@@ -40,7 +40,8 @@ dependencies = [
40
40
  "py4web",
41
41
  "yatl",
42
42
  "pydal",
43
- "configurable-json >= 1.1"
43
+ "configurable-json >= 1.1",
44
+ "typing-extensions",
44
45
  ]
45
46
 
46
47
 
@@ -0,0 +1 @@
1
+ __version__ = "1.3.1"
@@ -1,8 +1,10 @@
1
1
  import ast
2
+ import inspect
2
3
  import json
3
4
  import sys
4
5
  import traceback
5
6
  import typing
7
+ from types import FrameType
6
8
 
7
9
  import pydal.objects
8
10
  from configurablejson import ConfigurableJsonEncoder, JSONRule
@@ -159,35 +161,130 @@ def dd(*data: typing.Any, fancy: bool = True, api: bool = False) -> None:
159
161
  T = typing.TypeVar("T")
160
162
 
161
163
 
162
- def dbg(value: T) -> T:
164
+ def _extract_dbg_variable_name(frame: FrameType) -> str:
165
+ # Read the full source of the caller file
166
+ with open(frame.f_code.co_filename, "r", encoding="utf-8") as f:
167
+ source_code = f.readlines()
168
+
169
+ # Extract the relevant lines (handle multiline statements)
170
+ full_source = ""
171
+ line_number = frame.f_lineno - 1
172
+ while line_number < len(source_code):
173
+ full_source += source_code[line_number].strip()
174
+ if full_source.count("(") == full_source.count(")"): # Ensure all parentheses are closed
175
+ break
176
+ line_number += 1
177
+
178
+ # Parse the AST
179
+ tree = ast.parse(full_source)
180
+
181
+ # Find the relevant call in the AST
182
+ var_name = ""
183
+ for node in ast.walk(tree):
184
+ if isinstance(node, ast.Expr) and isinstance(node.value, ast.Call):
185
+ call = node.value
186
+ while isinstance(call, ast.Call): # Handle nested function calls
187
+ if hasattr(call.func, "id") and call.func.id == "dbg":
188
+ var_name = ast.unparse(call.args[0])
189
+ break
190
+ elif isinstance(call.func, ast.Attribute) and call.func.attr == "dbg":
191
+ var_name = ast.unparse(call.args[0])
192
+ break
193
+ call = call.args[0] if call.args else None
194
+ elif isinstance(node, ast.Assign) and isinstance(node.value, ast.Call):
195
+ call = node.value
196
+ while isinstance(call, ast.Call): # Handle assignments with nested calls
197
+ if hasattr(call.func, "id") and call.func.id == "dbg":
198
+ var_name = ast.unparse(call.args[0])
199
+ break
200
+ elif isinstance(call.func, ast.Attribute) and call.func.attr == "dbg":
201
+ var_name = ast.unparse(call.args[0])
202
+ break
203
+ call = call.args[0] if call.args else None
204
+ elif isinstance(node, ast.Return) and isinstance(node.value, ast.Call):
205
+ call = node.value
206
+ while isinstance(call, ast.Call): # Handle return dbg(x)
207
+ if hasattr(call.func, "id") and call.func.id == "dbg":
208
+ var_name = ast.unparse(call.args[0])
209
+ break
210
+ elif isinstance(call.func, ast.Attribute) and call.func.attr == "dbg":
211
+ var_name = ast.unparse(call.args[0])
212
+ break
213
+ call = call.args[0] if call.args else None
214
+ elif isinstance(node, ast.keyword) and isinstance(node.value, ast.Call):
215
+ # Handle function keyword arguments like some_func(arg=dbg("arg"))
216
+ call = node.value
217
+ if hasattr(call.func, "id") and call.func.id == "dbg":
218
+ var_name = ast.unparse(call.args[0])
219
+ break
220
+ elif isinstance(call.func, ast.Attribute) and call.func.attr == "dbg":
221
+ var_name = ast.unparse(call.args[0])
222
+ break
223
+
224
+ return var_name
225
+
226
+
227
+ def extract_dbg_variable_name(frame: FrameType) -> typing.Optional[str]:
228
+ """
229
+ Extracts the variable name passed to dbg() from the source code.
230
+ Handles multiline statements, assignments, function arguments, and return values.
163
231
  """
164
- Helper inspired by Rust's `dbg!`.
232
+ try:
233
+ return _extract_dbg_variable_name(frame)
234
+ except Exception:
235
+ return None
236
+
165
237
 
166
- Prints out the file, line, variable name, type and value.
167
- Also returns the original value.
238
+ def dbg(value: T) -> T:
168
239
  """
169
- value_type = type(value).__name__
240
+ Improved Rust-like dbg function that captures variable names even in assignments,
241
+ namespaced calls, and nested function calls.
242
+ Handles multiline calls, function arguments, and return statements.
170
243
 
171
- frame = traceback.extract_stack()[-2] # Get caller frame info
172
- file_name = frame.filename.split("/")[-1]
173
- line_number = frame.lineno
244
+ Examples:
174
245
 
175
- try:
176
- with open(frame.filename, "r") as f:
177
- source_code = f.readlines()
246
+ ```
247
+ def handle(_):
248
+ ...
178
249
 
179
- line = source_code[line_number - 1].strip()
180
- tree = ast.parse(line)
250
+ def my_decorator(fn): return fn
181
251
 
182
- var_name = None
183
- if isinstance(tree, ast.Module) and isinstance(tree.body[0], ast.Expr):
184
- call = tree.body[0].value
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
252
+ @my_decorator
253
+ def some_func(arg):
254
+ return dbg("hello")
187
255
 
188
- except Exception:
189
- var_name = "?"
190
256
 
191
- print(f"[{file_name}:{line_number}] {value_type}({var_name}) = {value}", file=sys.stderr)
257
+ dbg('hi')
258
+ y = dbg('hi')
259
+ py4web_debug.dbg('hi')
260
+ z = py4web_debug.dbg('hi')
192
261
 
262
+ handle(dbg('hi'))
263
+ handle(py4web_debug.dbg('hi'))
264
+
265
+ some_long_expression = " 3 + 123"
266
+
267
+ handle(
268
+ dbg(
269
+ (
270
+ some_long_expression +
271
+ some_long_expression
272
+ )
273
+ )
274
+ )
275
+
276
+ some_func(arg=dbg("arg1"))
277
+ ```
278
+ """
279
+ value_type = type(value).__name__
280
+
281
+ # Get the caller's frame
282
+ frame = inspect.currentframe().f_back
283
+ frame_info = inspect.getframeinfo(frame)
284
+ file_name = frame_info.filename.split("/")[-1]
285
+ line_number = frame_info.lineno
286
+
287
+ var_name = extract_dbg_variable_name(frame) or "?"
288
+
289
+ print(f"[{file_name}:{line_number}] {value_type}({var_name}) = {value}", file=sys.stderr)
193
290
  return value
@@ -1 +0,0 @@
1
- __version__ = "1.3.0"