FazBrowse GitHub Viewer
|
Trending
|
URL:
|
Home
Tools:
[Download Repo ZIP]
[View Raw Code]
[Original HTTPS Page]
pythonnative/src/pythonnative/diagnostics.py at main · andevsr/pythonnative · GitHub
andevsr
pythonnative
Repository navigation
Code
Pull requests
Actions
Projects
Security and quality
Insights
Expand file tree
Breadcrumbs
pythonnative
/
src
/
pythonnative
/
diagnostics.py
Copy path
More file actions
More file actions
Latest commit
History
History
History
241 lines (192 loc) · 7.8 KB
Breadcrumbs
pythonnative
/
src
/
pythonnative
/
diagnostics.py
Copy path
File metadata and controls
241 lines (192 loc) · 7.8 KB
Raw
Copy raw file
Download raw file
Open symbols panel
Edit and raw actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
"""Developer diagnostics: dev mode, warnings, and error reporting.
PythonNative distinguishes **dev mode** (the `pn preview` window, `pn
run` with hot reload, or any process with ``PN_DEV=1``) from production.
Dev mode turns on:
- **Validation warnings** ([`warn`][pythonnative.diagnostics.warn] /
[`warn_once`][pythonnative.diagnostics.warn_once]): unknown style
keys, duplicate list keys, and similar mistakes are printed once with
a suggestion instead of failing silently.
- **Hook-order checking**: calling hooks conditionally corrupts slot
state; in dev mode the mismatch raises a
[`HookOrderError`][pythonnative.diagnostics.HookOrderError]
immediately instead of cross-wiring state.
- **The RedBox**: uncaught errors from render, effects, and event
handlers are routed to the screen host, which presents a full-screen
error overlay (see `pythonnative.screen`) instead of crashing or
swallowing the traceback.
In production none of this runs: validation is skipped, hook-order
checks are skipped, and errors propagate exactly as raised.
This module has no dependencies on the rest of PythonNative, so any
module (hooks, reconciler, events) may import it freely.
"""
import
os
import
sys
import
threading
import
traceback
from
collections
import
deque
from
typing
import
Any
,
Callable
,
Deque
,
List
,
Optional
,
Set
,
Tuple
__all__
=
[
"HookOrderError"
,
"set_dev_mode"
,
"is_dev"
,
"warn"
,
"warn_once"
,
"swallowed"
,
"get_warnings"
,
"clear_warnings"
,
"set_error_reporter"
,
"report_error"
,
]
class
HookOrderError
(
RuntimeError
):
"""Raised in dev mode when hooks are called in a different order than the previous render.
Hooks map to state slots by call order, so calling them inside
conditionals or loops (or returning early between hook calls)
silently cross-wires state in production. Dev mode detects the
mismatch and raises this error with the offending component and
slot so the bug is caught at the source.
"""
# ======================================================================
# Dev mode
# ======================================================================
# Tri-state: ``None`` means "not explicitly set, consult the environment".
_dev_mode
:
Optional
[
bool
]
=
None
def
set_dev_mode
(
enabled
:
bool
)
->
None
:
"""Explicitly enable or disable dev mode for this process.
Called automatically by ``pn preview`` and by the screen host's
``enable_hot_reload`` (which the device templates invoke on debug
builds). An explicit call wins over the ``PN_DEV`` environment
variable.
Args:
enabled: ``True`` to turn on dev diagnostics.
"""
global
_dev_mode
_dev_mode
=
bool
(
enabled
)
def
is_dev
()
->
bool
:
"""Return whether dev diagnostics are active.
Resolution order: an explicit
[`set_dev_mode`][pythonnative.diagnostics.set_dev_mode] call, then
the ``PN_DEV`` environment variable, then ``False``.
"""
if
_dev_mode
is
not
None
:
return
_dev_mode
return
os
.
environ
.
get
(
"PN_DEV"
,
""
).
lower
()
in
{
"1"
,
"true"
,
"yes"
,
"on"
}
# ======================================================================
# Warnings (LogBox-lite)
# ======================================================================
_MAX_WARNINGS
=
200
_warn_lock
=
threading
.
Lock
()
_warned_keys
:
Set
[
str
]
=
set
()
_warnings
:
Deque
[
str
]
=
deque
(
maxlen
=
_MAX_WARNINGS
)
def
warn
(
message
:
str
)
->
None
:
"""Print a dev warning and record it in the warning log.
No-op in production. Warnings are prefixed with ``[PN] WARN`` on
stderr and retained (most recent 200) for inspection via
[`get_warnings`][pythonnative.diagnostics.get_warnings].
Args:
message: Human-readable description of the problem, ideally
with a suggestion for the fix.
"""
if
not
is_dev
():
return
with
_warn_lock
:
_warnings
.
append
(
message
)
try
:
print
(
f"[PN] WARN:
{
message
}
"
,
file
=
sys
.
stderr
,
flush
=
True
)
except
Exception
:
pass
def
warn_once
(
message
:
str
,
key
:
Optional
[
str
]
=
None
)
->
None
:
"""Like [`warn`][pythonnative.diagnostics.warn], but at most once per ``key``.
Use for per-render validation (style keys, list keys) so a warning
fires once instead of sixty times a second.
Args:
message: The warning message.
key: Dedupe key, e.g. ``"style:Text:font_siez"``. Defaults to
the message itself.
"""
if
not
is_dev
():
return
dedupe
=
key
if
key
is
not
None
else
message
with
_warn_lock
:
if
dedupe
in
_warned_keys
:
return
_warned_keys
.
add
(
dedupe
)
warn
(
message
)
def
swallowed
(
context
:
str
)
->
None
:
"""Surface a suppressed native-backend exception (dev mode only).
The native view/module backends intentionally degrade gracefully in
production: a failed style application or a missing OS API must not
crash the app. Call this from the ``except`` block instead of
``pass`` so that, in dev mode, each suppression is reported once per
call site instead of disappearing.
Args:
context: Where the suppression happened, e.g.
``"ios.TextHandler._apply"``.
"""
if
not
is_dev
():
return
exc
=
sys
.
exc_info
()[
1
]
if
exc
is
None
:
return
frame
=
sys
.
_getframe
(
1
)
location
=
f"
{
os
.
path
.
basename
(
frame
.
f_code
.
co_filename
)
}
:
{
frame
.
f_lineno
}
"
warn_once
(
f"
{
context
}
suppressed
{
type
(
exc
).
__name__
}
:
{
exc
}
(
{
location
}
)"
,
key
=
f"swallowed:
{
context
}
:
{
location
}
"
,
)
def
get_warnings
()
->
List
[
str
]:
"""Return a snapshot of the recorded warnings (oldest first)."""
with
_warn_lock
:
return
list
(
_warnings
)
def
clear_warnings
()
->
None
:
"""Drop all recorded warnings and dedupe keys (test helper)."""
with
_warn_lock
:
_warnings
.
clear
()
_warned_keys
.
clear
()
# ======================================================================
# Error reporting (RedBox routing)
# ======================================================================
#
# Screen hosts register themselves as error reporters. When an error
# escapes user code in a context that would otherwise be swallowed
# (event handlers) or crash the process (async tasks), dev mode routes
# it to the most recently registered reporter, which shows the RedBox.
# Reporters form a stack: the top entry is the most recently created
# (and therefore frontmost) screen.
_reporter_lock
=
threading
.
Lock
()
_reporters
:
List
[
Tuple
[
int
,
Callable
[[
BaseException
,
str
],
None
]]]
=
[]
def
set_error_reporter
(
owner
:
Any
,
reporter
:
Optional
[
Callable
[[
BaseException
,
str
],
None
]])
->
None
:
"""Register or unregister ``owner``'s RedBox reporter.
Args:
owner: Any object identifying the registration (a screen
host); keyed by ``id(owner)``.
reporter: ``reporter(exc, phase)`` callable, or ``None`` to
unregister the owner's reporter.
"""
key
=
id
(
owner
)
with
_reporter_lock
:
_reporters
[:]
=
[(
k
,
r
)
for
(
k
,
r
)
in
_reporters
if
k
!=
key
]
if
reporter
is
not
None
:
_reporters
.
append
((
key
,
reporter
))
def
report_error
(
exc
:
BaseException
,
phase
:
str
=
"runtime"
)
->
bool
:
"""Route ``exc`` to the active RedBox reporter (dev mode only).
Args:
exc: The exception to display.
phase: Where it came from: ``"render"``, ``"effect"``,
``"event"``, or ``"async"``.
Returns:
``True`` when a reporter accepted the error, ``False`` when no
reporter is registered (or dev mode is off), in which case the
caller should fall back to its default behavior.
"""
if
not
is_dev
():
return
False
with
_reporter_lock
:
reporter
=
_reporters
[
-
1
][
1
]
if
_reporters
else
None
if
reporter
is
None
:
return
False
try
:
reporter
(
exc
,
phase
)
return
True
except
Exception
:
traceback
.
print_exc
()
return
False
Back
|
FazBrowse Home
|
New Git URL