py4web-debug-tools 1.2.2__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.
- {py4web_debug_tools-1.2.2 → py4web_debug_tools-1.3.1}/CHANGELOG.md +12 -0
- {py4web_debug_tools-1.2.2 → py4web_debug_tools-1.3.1}/PKG-INFO +4 -3
- py4web_debug_tools-1.3.1/example.py +29 -0
- {py4web_debug_tools-1.2.2 → py4web_debug_tools-1.3.1}/pyproject.toml +2 -1
- py4web_debug_tools-1.3.1/src/py4web_debug/__about__.py +1 -0
- {py4web_debug_tools-1.2.2 → py4web_debug_tools-1.3.1}/src/py4web_debug/__init__.py +2 -1
- py4web_debug_tools-1.3.1/src/py4web_debug/dumping.py +290 -0
- py4web_debug_tools-1.2.2/src/py4web_debug/__about__.py +0 -1
- py4web_debug_tools-1.2.2/src/py4web_debug/dumping.py +0 -154
- {py4web_debug_tools-1.2.2 → py4web_debug_tools-1.3.1}/.gitignore +0 -0
- {py4web_debug_tools-1.2.2 → py4web_debug_tools-1.3.1}/README.md +0 -0
- {py4web_debug_tools-1.2.2 → py4web_debug_tools-1.3.1}/src/py4web_debug/_internals.py +0 -0
- {py4web_debug_tools-1.2.2 → py4web_debug_tools-1.3.1}/src/py4web_debug/core.py +0 -0
- {py4web_debug_tools-1.2.2 → py4web_debug_tools-1.3.1}/src/py4web_debug/debugbar.py +0 -0
- {py4web_debug_tools-1.2.2 → py4web_debug_tools-1.3.1}/src/py4web_debug/env.py +0 -0
- {py4web_debug_tools-1.2.2 → py4web_debug_tools-1.3.1}/src/py4web_debug/internals.py +0 -0
- {py4web_debug_tools-1.2.2 → py4web_debug_tools-1.3.1}/src/py4web_debug/py.typed +0 -0
- {py4web_debug_tools-1.2.2 → py4web_debug_tools-1.3.1}/src/py4web_debug/templates/debugbar.html +0 -0
- {py4web_debug_tools-1.2.2 → py4web_debug_tools-1.3.1}/src/py4web_debug/templates/dumpdie.html +0 -0
- {py4web_debug_tools-1.2.2 → py4web_debug_tools-1.3.1}/src/py4web_debug/templates/error_default.html +0 -0
- {py4web_debug_tools-1.2.2 → py4web_debug_tools-1.3.1}/src/py4web_debug/templates/fancy_dumpdie.html +0 -0
- {py4web_debug_tools-1.2.2 → py4web_debug_tools-1.3.1}/src/py4web_debug/types.py +0 -0
- {py4web_debug_tools-1.2.2 → py4web_debug_tools-1.3.1}/src/py4web_debug/wsgi.py +0 -0
|
@@ -2,6 +2,18 @@
|
|
|
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
|
+
|
|
11
|
+
## v1.3.0 (2025-04-01)
|
|
12
|
+
|
|
13
|
+
### Feature
|
|
14
|
+
|
|
15
|
+
* Add `dbg` helper ([`b7529e3`](https://github.com/trialandsuccess/py4web-debug-tools/commit/b7529e32bcaa88a237fa505d26afe3f013d0bdf2))
|
|
16
|
+
|
|
5
17
|
## v1.2.2 (2024-11-20)
|
|
6
18
|
|
|
7
19
|
### Fix
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: py4web-debug-tools
|
|
3
|
-
Version: 1.
|
|
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
|
|
7
7
|
Project-URL: Source, https://github.com/trialandsuccess/py4web-debug-tools
|
|
8
8
|
Author-email: Robin van der Noord <contact@trialandsuccess.nl>
|
|
9
|
-
License: MIT
|
|
9
|
+
License-Expression: MIT
|
|
10
10
|
Classifier: Development Status :: 4 - Beta
|
|
11
11
|
Classifier: Programming Language :: Python
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -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"))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.3.1"
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
from .core import tools
|
|
4
4
|
from .debugbar import catch
|
|
5
|
-
from .dumping import dd, dump
|
|
5
|
+
from .dumping import dbg, dd, dump
|
|
6
6
|
from .env import is_debug
|
|
7
7
|
from .wsgi import wsgi
|
|
8
8
|
|
|
9
9
|
__all__ = [
|
|
10
10
|
"tools",
|
|
11
11
|
"catch",
|
|
12
|
+
"dbg",
|
|
12
13
|
"dd",
|
|
13
14
|
"dump",
|
|
14
15
|
"is_debug",
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import inspect
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
import traceback
|
|
6
|
+
import typing
|
|
7
|
+
from types import FrameType
|
|
8
|
+
|
|
9
|
+
import pydal.objects
|
|
10
|
+
from configurablejson import ConfigurableJsonEncoder, JSONRule
|
|
11
|
+
from py4web import response
|
|
12
|
+
|
|
13
|
+
from .env import is_debug
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PossiblyAsDict(typing.Protocol):
|
|
17
|
+
def as_dict(self) -> dict[str, typing.Any]: ...
|
|
18
|
+
|
|
19
|
+
def asdict(self) -> dict[str, typing.Any]: ...
|
|
20
|
+
|
|
21
|
+
def _asdict(self) -> dict[str, typing.Any]: ...
|
|
22
|
+
|
|
23
|
+
def _as_dict(self) -> dict[str, typing.Any]: ...
|
|
24
|
+
|
|
25
|
+
def to_dict(self) -> dict[str, typing.Any]: ...
|
|
26
|
+
|
|
27
|
+
def todict(self) -> dict[str, typing.Any]: ...
|
|
28
|
+
|
|
29
|
+
def _todict(self) -> dict[str, typing.Any]: ...
|
|
30
|
+
|
|
31
|
+
def _to_dict(self) -> dict[str, typing.Any]: ...
|
|
32
|
+
|
|
33
|
+
def __json__(self) -> dict[str, typing.Any]: ...
|
|
34
|
+
|
|
35
|
+
def as_list(self) -> list[typing.Any]: ...
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class DDJsonEncoder(ConfigurableJsonEncoder):
|
|
39
|
+
@staticmethod
|
|
40
|
+
def _default(o: PossiblyAsDict) -> dict[str, typing.Any] | list[typing.Any] | str:
|
|
41
|
+
if hasattr(o, "as_list"):
|
|
42
|
+
# note: prefer as_list now as not every Rows may have an id (= default key of as_dict)
|
|
43
|
+
return o.as_list()
|
|
44
|
+
# more as list stuff?
|
|
45
|
+
|
|
46
|
+
if hasattr(o, "as_dict"):
|
|
47
|
+
return o.as_dict()
|
|
48
|
+
elif hasattr(o, "asdict"):
|
|
49
|
+
return o.asdict()
|
|
50
|
+
elif hasattr(o, "_asdict"):
|
|
51
|
+
return o._asdict()
|
|
52
|
+
elif hasattr(o, "_as_dict"):
|
|
53
|
+
return o._as_dict()
|
|
54
|
+
elif hasattr(o, "to_dict"):
|
|
55
|
+
return o.to_dict()
|
|
56
|
+
elif hasattr(o, "todict"):
|
|
57
|
+
return o.todict()
|
|
58
|
+
elif hasattr(o, "_todict"):
|
|
59
|
+
return o._todict()
|
|
60
|
+
elif hasattr(o, "_to_dict"):
|
|
61
|
+
return o._to_dict()
|
|
62
|
+
elif hasattr(o, "__json__"):
|
|
63
|
+
return o.__json__()
|
|
64
|
+
|
|
65
|
+
return str(o)
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def is_probably_namedtuple(o: typing.Any) -> bool:
|
|
69
|
+
"""
|
|
70
|
+
Try to guess if 'o' is a Named Tuple or something else.
|
|
71
|
+
"""
|
|
72
|
+
return isinstance(o, tuple) and hasattr(o, "_fields")
|
|
73
|
+
|
|
74
|
+
def rules(self, o: typing.Any, with_default: bool = True) -> JSONRule:
|
|
75
|
+
"""
|
|
76
|
+
Custom rules for the DD json: set to list and namedtuple to dict
|
|
77
|
+
# NOTE: with_default is (probably) false anyway!!!!
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
_type = typing.NamedTuple if self.is_probably_namedtuple(o) else type(o)
|
|
81
|
+
|
|
82
|
+
rules: dict[type, JSONRule] = {
|
|
83
|
+
# convert set to list
|
|
84
|
+
set: JSONRule(preprocess=lambda o: list(o)),
|
|
85
|
+
# convert namedtuple to dict
|
|
86
|
+
typing.NamedTuple: JSONRule(preprocess=self._default),
|
|
87
|
+
pydal.objects.Row: JSONRule(preprocess=lambda row: row.as_dict()),
|
|
88
|
+
pydal.objects.Rows: JSONRule(preprocess=lambda row: row.as_list()),
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if rule := rules.get(typing.cast(type, _type)):
|
|
92
|
+
return rule
|
|
93
|
+
elif rule is None and with_default:
|
|
94
|
+
return JSONRule(transform=self._default)
|
|
95
|
+
else:
|
|
96
|
+
# empty rule:
|
|
97
|
+
return JSONRule()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def dump(data: typing.Iterable[typing.Any], with_headers: bool = True) -> str:
|
|
101
|
+
"""
|
|
102
|
+
Helper to json dump some data with custom converter and headers
|
|
103
|
+
"""
|
|
104
|
+
if with_headers:
|
|
105
|
+
response.headers["Content-Type"] = "application/json"
|
|
106
|
+
|
|
107
|
+
return json.dumps(data, indent=2, cls=DDJsonEncoder)
|
|
108
|
+
# return json.dumps(data, indent=2, default=DDJsonEncoder._default)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class DumpDieError(Exception):
|
|
112
|
+
"""
|
|
113
|
+
Custom Exception used to catch dd() in py4web error page -> regular output
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class FancyDumpDieError(Exception):
|
|
120
|
+
"""
|
|
121
|
+
Custom Exception used to catch dd() in py4web error page -> styled output
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class ApiDumpDieError(Exception):
|
|
128
|
+
"""
|
|
129
|
+
Custom Exception used to catch dd() in py4web error page -> JSON only output
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def dd(*data: typing.Any, fancy: bool = True, api: bool = False) -> None:
|
|
136
|
+
"""
|
|
137
|
+
Dump and Die:
|
|
138
|
+
Show args as JSON an halt the rest of the request
|
|
139
|
+
(useful for debugging fifty levels deep in some helper method)
|
|
140
|
+
|
|
141
|
+
"""
|
|
142
|
+
if len(data) == 1:
|
|
143
|
+
data = data[0]
|
|
144
|
+
|
|
145
|
+
if not is_debug():
|
|
146
|
+
# simpler:
|
|
147
|
+
print("dd |", data, file=sys.stderr)
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
# if is_debug: more complex error
|
|
151
|
+
_json = dump(data)
|
|
152
|
+
if api:
|
|
153
|
+
# todo: detect automatically?
|
|
154
|
+
raise ApiDumpDieError(_json)
|
|
155
|
+
elif fancy:
|
|
156
|
+
raise FancyDumpDieError(_json)
|
|
157
|
+
else:
|
|
158
|
+
raise DumpDieError(_json)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
T = typing.TypeVar("T")
|
|
162
|
+
|
|
163
|
+
|
|
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.
|
|
231
|
+
"""
|
|
232
|
+
try:
|
|
233
|
+
return _extract_dbg_variable_name(frame)
|
|
234
|
+
except Exception:
|
|
235
|
+
return None
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def dbg(value: T) -> T:
|
|
239
|
+
"""
|
|
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.
|
|
243
|
+
|
|
244
|
+
Examples:
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
def handle(_):
|
|
248
|
+
...
|
|
249
|
+
|
|
250
|
+
def my_decorator(fn): return fn
|
|
251
|
+
|
|
252
|
+
@my_decorator
|
|
253
|
+
def some_func(arg):
|
|
254
|
+
return dbg("hello")
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
dbg('hi')
|
|
258
|
+
y = dbg('hi')
|
|
259
|
+
py4web_debug.dbg('hi')
|
|
260
|
+
z = py4web_debug.dbg('hi')
|
|
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)
|
|
290
|
+
return value
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.2.2"
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import sys
|
|
3
|
-
import typing
|
|
4
|
-
|
|
5
|
-
import pydal.objects
|
|
6
|
-
from configurablejson import ConfigurableJsonEncoder, JSONRule
|
|
7
|
-
from py4web import response
|
|
8
|
-
|
|
9
|
-
from .env import is_debug
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class PossiblyAsDict(typing.Protocol):
|
|
13
|
-
def as_dict(self) -> dict[str, typing.Any]: ...
|
|
14
|
-
|
|
15
|
-
def asdict(self) -> dict[str, typing.Any]: ...
|
|
16
|
-
|
|
17
|
-
def _asdict(self) -> dict[str, typing.Any]: ...
|
|
18
|
-
|
|
19
|
-
def _as_dict(self) -> dict[str, typing.Any]: ...
|
|
20
|
-
|
|
21
|
-
def to_dict(self) -> dict[str, typing.Any]: ...
|
|
22
|
-
|
|
23
|
-
def todict(self) -> dict[str, typing.Any]: ...
|
|
24
|
-
|
|
25
|
-
def _todict(self) -> dict[str, typing.Any]: ...
|
|
26
|
-
|
|
27
|
-
def _to_dict(self) -> dict[str, typing.Any]: ...
|
|
28
|
-
|
|
29
|
-
def __json__(self) -> dict[str, typing.Any]: ...
|
|
30
|
-
|
|
31
|
-
def as_list(self) -> list[typing.Any]: ...
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class DDJsonEncoder(ConfigurableJsonEncoder):
|
|
35
|
-
@staticmethod
|
|
36
|
-
def _default(o: PossiblyAsDict) -> dict[str, typing.Any] | list[typing.Any] | str:
|
|
37
|
-
if hasattr(o, "as_list"):
|
|
38
|
-
# note: prefer as_list now as not every Rows may have an id (= default key of as_dict)
|
|
39
|
-
return o.as_list()
|
|
40
|
-
# more as list stuff?
|
|
41
|
-
|
|
42
|
-
if hasattr(o, "as_dict"):
|
|
43
|
-
return o.as_dict()
|
|
44
|
-
elif hasattr(o, "asdict"):
|
|
45
|
-
return o.asdict()
|
|
46
|
-
elif hasattr(o, "_asdict"):
|
|
47
|
-
return o._asdict()
|
|
48
|
-
elif hasattr(o, "_as_dict"):
|
|
49
|
-
return o._as_dict()
|
|
50
|
-
elif hasattr(o, "to_dict"):
|
|
51
|
-
return o.to_dict()
|
|
52
|
-
elif hasattr(o, "todict"):
|
|
53
|
-
return o.todict()
|
|
54
|
-
elif hasattr(o, "_todict"):
|
|
55
|
-
return o._todict()
|
|
56
|
-
elif hasattr(o, "_to_dict"):
|
|
57
|
-
return o._to_dict()
|
|
58
|
-
elif hasattr(o, "__json__"):
|
|
59
|
-
return o.__json__()
|
|
60
|
-
|
|
61
|
-
return str(o)
|
|
62
|
-
|
|
63
|
-
@staticmethod
|
|
64
|
-
def is_probably_namedtuple(o: typing.Any) -> bool:
|
|
65
|
-
"""
|
|
66
|
-
Try to guess if 'o' is a Named Tuple or something else.
|
|
67
|
-
"""
|
|
68
|
-
return isinstance(o, tuple) and hasattr(o, "_fields")
|
|
69
|
-
|
|
70
|
-
def rules(self, o: typing.Any, with_default: bool = True) -> JSONRule:
|
|
71
|
-
"""
|
|
72
|
-
Custom rules for the DD json: set to list and namedtuple to dict
|
|
73
|
-
# NOTE: with_default is (probably) false anyway!!!!
|
|
74
|
-
"""
|
|
75
|
-
|
|
76
|
-
_type = typing.NamedTuple if self.is_probably_namedtuple(o) else type(o)
|
|
77
|
-
|
|
78
|
-
rules: dict[type, JSONRule] = {
|
|
79
|
-
# convert set to list
|
|
80
|
-
set: JSONRule(preprocess=lambda o: list(o)),
|
|
81
|
-
# convert namedtuple to dict
|
|
82
|
-
typing.NamedTuple: JSONRule(preprocess=self._default),
|
|
83
|
-
pydal.objects.Row: JSONRule(preprocess=lambda row: row.as_dict()),
|
|
84
|
-
pydal.objects.Rows: JSONRule(preprocess=lambda row: row.as_list()),
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if rule := rules.get(typing.cast(type, _type)):
|
|
88
|
-
return rule
|
|
89
|
-
elif rule is None and with_default:
|
|
90
|
-
return JSONRule(transform=self._default)
|
|
91
|
-
else:
|
|
92
|
-
# empty rule:
|
|
93
|
-
return JSONRule()
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def dump(data: typing.Iterable[typing.Any], with_headers: bool = True) -> str:
|
|
97
|
-
"""
|
|
98
|
-
Helper to json dump some data with custom converter and headers
|
|
99
|
-
"""
|
|
100
|
-
if with_headers:
|
|
101
|
-
response.headers["Content-Type"] = "application/json"
|
|
102
|
-
|
|
103
|
-
return json.dumps(data, indent=2, cls=DDJsonEncoder)
|
|
104
|
-
# return json.dumps(data, indent=2, default=DDJsonEncoder._default)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
class DumpDieError(Exception):
|
|
108
|
-
"""
|
|
109
|
-
Custom Exception used to catch dd() in py4web error page -> regular output
|
|
110
|
-
"""
|
|
111
|
-
|
|
112
|
-
pass
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
class FancyDumpDieError(Exception):
|
|
116
|
-
"""
|
|
117
|
-
Custom Exception used to catch dd() in py4web error page -> styled output
|
|
118
|
-
"""
|
|
119
|
-
|
|
120
|
-
pass
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
class ApiDumpDieError(Exception):
|
|
124
|
-
"""
|
|
125
|
-
Custom Exception used to catch dd() in py4web error page -> JSON only output
|
|
126
|
-
"""
|
|
127
|
-
|
|
128
|
-
pass
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def dd(*data: typing.Any, fancy: bool = True, api: bool = False) -> None:
|
|
132
|
-
"""
|
|
133
|
-
Dump and Die:
|
|
134
|
-
Show args as JSON an halt the rest of the request
|
|
135
|
-
(useful for debugging fifty levels deep in some helper method)
|
|
136
|
-
|
|
137
|
-
"""
|
|
138
|
-
if len(data) == 1:
|
|
139
|
-
data = data[0]
|
|
140
|
-
|
|
141
|
-
if not is_debug():
|
|
142
|
-
# simpler:
|
|
143
|
-
print("dd |", data, file=sys.stderr)
|
|
144
|
-
return
|
|
145
|
-
|
|
146
|
-
# if is_debug: more complex error
|
|
147
|
-
_json = dump(data)
|
|
148
|
-
if api:
|
|
149
|
-
# todo: detect automatically?
|
|
150
|
-
raise ApiDumpDieError(_json)
|
|
151
|
-
elif fancy:
|
|
152
|
-
raise FancyDumpDieError(_json)
|
|
153
|
-
else:
|
|
154
|
-
raise DumpDieError(_json)
|
|
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.2.2 → py4web_debug_tools-1.3.1}/src/py4web_debug/templates/debugbar.html
RENAMED
|
File without changes
|
{py4web_debug_tools-1.2.2 → py4web_debug_tools-1.3.1}/src/py4web_debug/templates/dumpdie.html
RENAMED
|
File without changes
|
{py4web_debug_tools-1.2.2 → py4web_debug_tools-1.3.1}/src/py4web_debug/templates/error_default.html
RENAMED
|
File without changes
|
{py4web_debug_tools-1.2.2 → py4web_debug_tools-1.3.1}/src/py4web_debug/templates/fancy_dumpdie.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|