sf-veritas 0.9.7__py3-none-any.whl
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.
Potentially problematic release.
This version of sf-veritas might be problematic. Click here for more details.
- sf_veritas/.gitignore +2 -0
- sf_veritas/__init__.py +4 -0
- sf_veritas/app_config.py +49 -0
- sf_veritas/cli.py +336 -0
- sf_veritas/constants.py +3 -0
- sf_veritas/custom_excepthook.py +285 -0
- sf_veritas/custom_log_handler.py +53 -0
- sf_veritas/custom_output_wrapper.py +107 -0
- sf_veritas/custom_print.py +34 -0
- sf_veritas/django_app.py +5 -0
- sf_veritas/env_vars.py +83 -0
- sf_veritas/exception_handling_middleware.py +18 -0
- sf_veritas/exception_metaclass.py +69 -0
- sf_veritas/frame_tools.py +112 -0
- sf_veritas/import_hook.py +62 -0
- sf_veritas/infra_details/__init__.py +3 -0
- sf_veritas/infra_details/get_infra_details.py +24 -0
- sf_veritas/infra_details/kubernetes/__init__.py +3 -0
- sf_veritas/infra_details/kubernetes/get_cluster_name.py +147 -0
- sf_veritas/infra_details/kubernetes/get_details.py +7 -0
- sf_veritas/infra_details/running_on/__init__.py +17 -0
- sf_veritas/infra_details/running_on/kubernetes.py +11 -0
- sf_veritas/interceptors.py +252 -0
- sf_veritas/local_env_detect.py +118 -0
- sf_veritas/package_metadata.py +6 -0
- sf_veritas/patches/__init__.py +0 -0
- sf_veritas/patches/concurrent_futures.py +19 -0
- sf_veritas/patches/constants.py +1 -0
- sf_veritas/patches/exceptions.py +82 -0
- sf_veritas/patches/multiprocessing.py +32 -0
- sf_veritas/patches/network_libraries/__init__.py +51 -0
- sf_veritas/patches/network_libraries/aiohttp.py +100 -0
- sf_veritas/patches/network_libraries/curl_cffi.py +93 -0
- sf_veritas/patches/network_libraries/http_client.py +64 -0
- sf_veritas/patches/network_libraries/httpcore.py +152 -0
- sf_veritas/patches/network_libraries/httplib2.py +76 -0
- sf_veritas/patches/network_libraries/httpx.py +123 -0
- sf_veritas/patches/network_libraries/niquests.py +192 -0
- sf_veritas/patches/network_libraries/pycurl.py +71 -0
- sf_veritas/patches/network_libraries/requests.py +187 -0
- sf_veritas/patches/network_libraries/tornado.py +139 -0
- sf_veritas/patches/network_libraries/treq.py +122 -0
- sf_veritas/patches/network_libraries/urllib_request.py +129 -0
- sf_veritas/patches/network_libraries/utils.py +101 -0
- sf_veritas/patches/os.py +17 -0
- sf_veritas/patches/threading.py +32 -0
- sf_veritas/patches/web_frameworks/__init__.py +45 -0
- sf_veritas/patches/web_frameworks/aiohttp.py +133 -0
- sf_veritas/patches/web_frameworks/async_websocket_consumer.py +132 -0
- sf_veritas/patches/web_frameworks/blacksheep.py +107 -0
- sf_veritas/patches/web_frameworks/bottle.py +142 -0
- sf_veritas/patches/web_frameworks/cherrypy.py +246 -0
- sf_veritas/patches/web_frameworks/django.py +307 -0
- sf_veritas/patches/web_frameworks/eve.py +138 -0
- sf_veritas/patches/web_frameworks/falcon.py +229 -0
- sf_veritas/patches/web_frameworks/fastapi.py +145 -0
- sf_veritas/patches/web_frameworks/flask.py +186 -0
- sf_veritas/patches/web_frameworks/klein.py +40 -0
- sf_veritas/patches/web_frameworks/litestar.py +217 -0
- sf_veritas/patches/web_frameworks/pyramid.py +89 -0
- sf_veritas/patches/web_frameworks/quart.py +155 -0
- sf_veritas/patches/web_frameworks/robyn.py +114 -0
- sf_veritas/patches/web_frameworks/sanic.py +120 -0
- sf_veritas/patches/web_frameworks/starlette.py +144 -0
- sf_veritas/patches/web_frameworks/strawberry.py +269 -0
- sf_veritas/patches/web_frameworks/tornado.py +129 -0
- sf_veritas/patches/web_frameworks/utils.py +55 -0
- sf_veritas/print_override.py +13 -0
- sf_veritas/regular_data_transmitter.py +358 -0
- sf_veritas/request_interceptor.py +399 -0
- sf_veritas/request_utils.py +104 -0
- sf_veritas/server_status.py +1 -0
- sf_veritas/shutdown_flag.py +11 -0
- sf_veritas/subprocess_startup.py +3 -0
- sf_veritas/test_cli.py +145 -0
- sf_veritas/thread_local.py +436 -0
- sf_veritas/timeutil.py +114 -0
- sf_veritas/transmit_exception_to_sailfish.py +28 -0
- sf_veritas/transmitter.py +58 -0
- sf_veritas/types.py +44 -0
- sf_veritas/unified_interceptor.py +323 -0
- sf_veritas/utils.py +39 -0
- sf_veritas-0.9.7.dist-info/METADATA +83 -0
- sf_veritas-0.9.7.dist-info/RECORD +86 -0
- sf_veritas-0.9.7.dist-info/WHEEL +4 -0
- sf_veritas-0.9.7.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
from importlib.util import find_spec
|
|
5
|
+
from typing import Any, Callable, Set, Tuple
|
|
6
|
+
|
|
7
|
+
from ...custom_excepthook import custom_excepthook
|
|
8
|
+
from ...env_vars import PRINT_CONFIGURATION_STATUSES, SF_DEBUG
|
|
9
|
+
from ...regular_data_transmitter import NetworkHopsTransmitter
|
|
10
|
+
from ...thread_local import get_or_set_sf_trace_id
|
|
11
|
+
from .utils import _is_user_code, _unwrap_user_func
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
# Track if Strawberry has already been patched to prevent multiple patches
|
|
16
|
+
_is_strawberry_patched = False
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_extension():
|
|
20
|
+
from strawberry.extensions import SchemaExtension
|
|
21
|
+
|
|
22
|
+
class CustomErrorHandlingExtension(SchemaExtension):
|
|
23
|
+
def __init__(self, *, execution_context):
|
|
24
|
+
self.execution_context = execution_context
|
|
25
|
+
|
|
26
|
+
def on_request_start(self):
|
|
27
|
+
if SF_DEBUG:
|
|
28
|
+
print("Starting GraphQL request", log=False)
|
|
29
|
+
|
|
30
|
+
def on_request_end(self):
|
|
31
|
+
if SF_DEBUG:
|
|
32
|
+
print("Ending GraphQL request", log=False)
|
|
33
|
+
if not self.execution_context.errors:
|
|
34
|
+
return
|
|
35
|
+
for error in self.execution_context.errors:
|
|
36
|
+
if SF_DEBUG:
|
|
37
|
+
print(f"Handling GraphQL error: {error}", log=False)
|
|
38
|
+
custom_excepthook(type(error), error, error.__traceback__)
|
|
39
|
+
|
|
40
|
+
def on_validation_start(self):
|
|
41
|
+
if SF_DEBUG:
|
|
42
|
+
print("Starting validation of GraphQL request", log=False)
|
|
43
|
+
|
|
44
|
+
def on_validation_end(self):
|
|
45
|
+
if SF_DEBUG:
|
|
46
|
+
print("Ending validation of GraphQL request", log=False)
|
|
47
|
+
|
|
48
|
+
def on_execution_start(self):
|
|
49
|
+
if SF_DEBUG:
|
|
50
|
+
print("Starting execution of GraphQL request", log=False)
|
|
51
|
+
|
|
52
|
+
def on_resolver_start(self, resolver, obj, info, **kwargs):
|
|
53
|
+
if SF_DEBUG:
|
|
54
|
+
print(f"Starting resolver {resolver.__name__}", log=False)
|
|
55
|
+
|
|
56
|
+
def on_resolver_end(self, resolver, obj, info, **kwargs):
|
|
57
|
+
if SF_DEBUG:
|
|
58
|
+
print(f"Ending resolver {resolver.__name__}", log=False)
|
|
59
|
+
|
|
60
|
+
def on_error(self, error: Exception):
|
|
61
|
+
if SF_DEBUG:
|
|
62
|
+
print(f"Handling error in resolver: {error}", log=False)
|
|
63
|
+
custom_excepthook(type(error), error, error.__traceback__)
|
|
64
|
+
|
|
65
|
+
return CustomErrorHandlingExtension
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_network_hop_extension() -> "type[SchemaExtension]":
|
|
69
|
+
"""
|
|
70
|
+
Strawberry SchemaExtension that emits a collectNetworkHops mutation for the
|
|
71
|
+
*first* user-land frame executed inside every resolver (sync or async).
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
from strawberry.extensions import SchemaExtension
|
|
75
|
+
|
|
76
|
+
# --------------------------------------------------------------------- #
|
|
77
|
+
# Helper predicates
|
|
78
|
+
# --------------------------------------------------------------------- #
|
|
79
|
+
# Extended dig: __wrapped__, closure cells *and* common attribute names
|
|
80
|
+
# --------------------------------------------------------------------- #
|
|
81
|
+
# Extension class
|
|
82
|
+
# --------------------------------------------------------------------- #
|
|
83
|
+
class NetworkHopExtension(SchemaExtension):
|
|
84
|
+
supports_sync = supports_async = True
|
|
85
|
+
_sent: Set[Tuple[str, int]] = set() # class-level: de-dupe per request
|
|
86
|
+
|
|
87
|
+
# ---------------- internal emit helper ---------------- #
|
|
88
|
+
@staticmethod
|
|
89
|
+
def _emit(frame, info):
|
|
90
|
+
filename, line_no, func_name = (
|
|
91
|
+
frame.f_code.co_filename,
|
|
92
|
+
frame.f_lineno,
|
|
93
|
+
frame.f_code.co_name,
|
|
94
|
+
)
|
|
95
|
+
if (filename, line_no) in NetworkHopExtension._sent:
|
|
96
|
+
return
|
|
97
|
+
_, session_id = get_or_set_sf_trace_id()
|
|
98
|
+
if SF_DEBUG:
|
|
99
|
+
print(
|
|
100
|
+
f"[[NetworkHopExtension]] SEND → {func_name} "
|
|
101
|
+
f"({filename}:{line_no}) session={session_id}",
|
|
102
|
+
log=False,
|
|
103
|
+
)
|
|
104
|
+
NetworkHopsTransmitter().send(
|
|
105
|
+
session_id=session_id,
|
|
106
|
+
line=str(line_no),
|
|
107
|
+
column="0",
|
|
108
|
+
name=func_name,
|
|
109
|
+
entrypoint=filename,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# ---------------- tracer factory ---------------- #
|
|
113
|
+
def _make_tracer(self, info):
|
|
114
|
+
def tracer(frame, event, arg):
|
|
115
|
+
if event.startswith("c_"):
|
|
116
|
+
return
|
|
117
|
+
if event == "call":
|
|
118
|
+
if _is_user_code(frame.f_code.co_filename):
|
|
119
|
+
|
|
120
|
+
self._emit(frame, info)
|
|
121
|
+
sys.setprofile(None)
|
|
122
|
+
return
|
|
123
|
+
return tracer # keep tracing until we hit user code
|
|
124
|
+
|
|
125
|
+
return tracer
|
|
126
|
+
|
|
127
|
+
# ---------------- wrappers ---------------- #
|
|
128
|
+
def resolve(self, _next, root, info, *args, **kwargs):
|
|
129
|
+
user_fn = _unwrap_user_func(_next)
|
|
130
|
+
tracer = self._make_tracer(info)
|
|
131
|
+
sys.setprofile(tracer)
|
|
132
|
+
try:
|
|
133
|
+
return _next(root, info, *args, **kwargs)
|
|
134
|
+
finally:
|
|
135
|
+
sys.setprofile(None) # safety-net
|
|
136
|
+
|
|
137
|
+
async def resolve_async(self, _next, root, info, *args, **kwargs):
|
|
138
|
+
user_fn = _unwrap_user_func(_next)
|
|
139
|
+
tracer = self._make_tracer(info)
|
|
140
|
+
sys.setprofile(tracer)
|
|
141
|
+
try:
|
|
142
|
+
return await _next(root, info, *args, **kwargs)
|
|
143
|
+
finally:
|
|
144
|
+
sys.setprofile(None)
|
|
145
|
+
|
|
146
|
+
return NetworkHopExtension
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def patch_strawberry_module(strawberry):
|
|
150
|
+
"""Patch Strawberry to ensure exceptions go through the custom excepthook."""
|
|
151
|
+
global _is_strawberry_patched
|
|
152
|
+
if _is_strawberry_patched:
|
|
153
|
+
if SF_DEBUG:
|
|
154
|
+
print(
|
|
155
|
+
"[[DEBUG]] Strawberry has already been patched, skipping. [[/DEBUG]]",
|
|
156
|
+
log=False,
|
|
157
|
+
)
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
# Backup the original execute method from Strawberry
|
|
162
|
+
original_execute = strawberry.execution.execute.execute
|
|
163
|
+
|
|
164
|
+
async def custom_execute(*args, **kwargs):
|
|
165
|
+
try:
|
|
166
|
+
if SF_DEBUG:
|
|
167
|
+
print(
|
|
168
|
+
"[[DEBUG]] Executing patched Strawberry execute function. [[/DEBUG]]",
|
|
169
|
+
log=False,
|
|
170
|
+
)
|
|
171
|
+
return await original_execute(*args, **kwargs)
|
|
172
|
+
except Exception as e:
|
|
173
|
+
if SF_DEBUG:
|
|
174
|
+
print(
|
|
175
|
+
"[[DEBUG]] Intercepted exception in Strawberry execute. [[/DEBUG]]",
|
|
176
|
+
log=False,
|
|
177
|
+
)
|
|
178
|
+
# Invoke custom excepthook globally
|
|
179
|
+
sys.excepthook(type(e), e, e.__traceback__)
|
|
180
|
+
raise
|
|
181
|
+
|
|
182
|
+
# Replace Strawberry's execute function with the patched version
|
|
183
|
+
strawberry.execution.execute.execute = custom_execute
|
|
184
|
+
_is_strawberry_patched = True
|
|
185
|
+
if SF_DEBUG:
|
|
186
|
+
print(
|
|
187
|
+
"[[DEBUG]] Successfully patched Strawberry execute function. [[/DEBUG]]",
|
|
188
|
+
log=False,
|
|
189
|
+
)
|
|
190
|
+
except Exception as error:
|
|
191
|
+
if SF_DEBUG:
|
|
192
|
+
print(
|
|
193
|
+
f"[[DEBUG]] Failed to patch Strawberry: {error}. [[/DEBUG]]", log=False
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class CustomImportHook:
|
|
198
|
+
"""Import hook to intercept the import of 'strawberry' modules."""
|
|
199
|
+
|
|
200
|
+
def find_spec(self, fullname, path, target=None):
|
|
201
|
+
global _is_strawberry_patched
|
|
202
|
+
if fullname == "strawberry" and not _is_strawberry_patched:
|
|
203
|
+
if SF_DEBUG:
|
|
204
|
+
print(
|
|
205
|
+
f"[[DEBUG]] Intercepting import of {fullname}. [[/DEBUG]]",
|
|
206
|
+
log=False,
|
|
207
|
+
)
|
|
208
|
+
return find_spec(fullname)
|
|
209
|
+
if fullname.startswith("strawberry_django"):
|
|
210
|
+
return None # Let default import handle strawberry_django
|
|
211
|
+
|
|
212
|
+
def exec_module(self, module):
|
|
213
|
+
if SF_DEBUG:
|
|
214
|
+
print(
|
|
215
|
+
f"[[DEBUG]] Executing module: {module.__name__}. [[/DEBUG]]", log=False
|
|
216
|
+
)
|
|
217
|
+
# Execute the module normally
|
|
218
|
+
module_spec = module.__spec__
|
|
219
|
+
if module_spec and module_spec.loader:
|
|
220
|
+
module_spec.loader.exec_module(module)
|
|
221
|
+
# Once strawberry is loaded, patch it
|
|
222
|
+
if module.__name__ == "strawberry" and not _is_strawberry_patched:
|
|
223
|
+
patch_strawberry_module(module)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def patch_strawberry_schema():
|
|
227
|
+
"""Patch strawberry.Schema to include both Sailfish and NetworkHop extensions by default."""
|
|
228
|
+
try:
|
|
229
|
+
import strawberry
|
|
230
|
+
|
|
231
|
+
original_schema_init = strawberry.Schema.__init__
|
|
232
|
+
|
|
233
|
+
def patched_schema_init(self, *args, extensions=None, **kwargs):
|
|
234
|
+
if extensions is None:
|
|
235
|
+
extensions = []
|
|
236
|
+
|
|
237
|
+
# Add the custom error handling extension
|
|
238
|
+
sailfish_ext = get_extension()
|
|
239
|
+
if sailfish_ext not in extensions:
|
|
240
|
+
extensions.append(sailfish_ext)
|
|
241
|
+
|
|
242
|
+
# Add the network hop extension
|
|
243
|
+
hop_ext = get_network_hop_extension()
|
|
244
|
+
if hop_ext not in extensions:
|
|
245
|
+
extensions.append(hop_ext)
|
|
246
|
+
|
|
247
|
+
# Call the original constructor
|
|
248
|
+
original_schema_init(self, *args, extensions=extensions, **kwargs)
|
|
249
|
+
|
|
250
|
+
if SF_DEBUG:
|
|
251
|
+
print(
|
|
252
|
+
"[[DEBUG]] Patched strawberry.Schema to include Sailfish & NetworkHop extensions. [[/DEBUG]]",
|
|
253
|
+
log=False,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Apply the patch
|
|
257
|
+
strawberry.Schema.__init__ = patched_schema_init
|
|
258
|
+
|
|
259
|
+
if SF_DEBUG:
|
|
260
|
+
print(
|
|
261
|
+
"[[DEBUG]] Successfully patched strawberry.Schema. [[/DEBUG]]",
|
|
262
|
+
log=False,
|
|
263
|
+
)
|
|
264
|
+
except ImportError:
|
|
265
|
+
if SF_DEBUG:
|
|
266
|
+
print(
|
|
267
|
+
"[[DEBUG]] Strawberry is not installed. Skipping schema patching. [[/DEBUG]]",
|
|
268
|
+
log=False,
|
|
269
|
+
)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import site
|
|
3
|
+
import sysconfig
|
|
4
|
+
from functools import lru_cache
|
|
5
|
+
from typing import Any, Callable, Set, Tuple
|
|
6
|
+
|
|
7
|
+
from ...constants import SAILFISH_TRACING_HEADER
|
|
8
|
+
from ...custom_excepthook import custom_excepthook
|
|
9
|
+
from ...env_vars import SF_DEBUG
|
|
10
|
+
from ...regular_data_transmitter import NetworkHopsTransmitter
|
|
11
|
+
from ...thread_local import get_or_set_sf_trace_id
|
|
12
|
+
from .utils import _is_user_code, _unwrap_user_func
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def patch_tornado():
|
|
16
|
+
"""
|
|
17
|
+
Monkey-patch tornado.web.RequestHandler so that every request:
|
|
18
|
+
|
|
19
|
+
1. Propagates SAILFISH_TRACING_HEADER into the ContextVar.
|
|
20
|
+
2. Emits ONE NetworkHop when user-land verb handler starts.
|
|
21
|
+
3. Funnels *all* exceptions—including tornado.web.HTTPError—through
|
|
22
|
+
custom_excepthook before Tornado's own error machinery runs.
|
|
23
|
+
|
|
24
|
+
Safe no-op if Tornado isn't installed.
|
|
25
|
+
"""
|
|
26
|
+
try:
|
|
27
|
+
import tornado.web
|
|
28
|
+
except ImportError: # Tornado not installed
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
# --------------------------------------------------------------- #
|
|
32
|
+
# a) Header capture + hop emission (prepare) – unchanged logic
|
|
33
|
+
# --------------------------------------------------------------- #
|
|
34
|
+
original_prepare = tornado.web.RequestHandler.prepare
|
|
35
|
+
|
|
36
|
+
def patched_prepare(self, *args, **kwargs):
|
|
37
|
+
# -- 1) Header propagation
|
|
38
|
+
header_val = self.request.headers.get(SAILFISH_TRACING_HEADER)
|
|
39
|
+
if header_val:
|
|
40
|
+
get_or_set_sf_trace_id(header_val, is_associated_with_inbound_request=True)
|
|
41
|
+
|
|
42
|
+
# -- 2) Emit hop once per request for the actual HTTP verb handler
|
|
43
|
+
method_name = self.request.method.lower()
|
|
44
|
+
handler_fn = getattr(self, method_name, None)
|
|
45
|
+
|
|
46
|
+
if callable(handler_fn):
|
|
47
|
+
module = getattr(handler_fn, "__module__", "")
|
|
48
|
+
if not module.startswith("strawberry"):
|
|
49
|
+
real_fn = _unwrap_user_func(handler_fn)
|
|
50
|
+
code_obj = getattr(real_fn, "__code__", None)
|
|
51
|
+
if code_obj and _is_user_code(code_obj.co_filename):
|
|
52
|
+
key = (code_obj.co_filename, code_obj.co_firstlineno)
|
|
53
|
+
if key not in SailfishHandlerPatch._sent:
|
|
54
|
+
_, session_id = get_or_set_sf_trace_id()
|
|
55
|
+
if SF_DEBUG:
|
|
56
|
+
print(
|
|
57
|
+
f"[[TornadoHop]] {real_fn.__name__} "
|
|
58
|
+
f"({code_obj.co_filename}:{code_obj.co_firstlineno}) "
|
|
59
|
+
f"session={session_id}",
|
|
60
|
+
log=False,
|
|
61
|
+
)
|
|
62
|
+
NetworkHopsTransmitter().send(
|
|
63
|
+
session_id=session_id,
|
|
64
|
+
line=str(code_obj.co_firstlineno),
|
|
65
|
+
column="0",
|
|
66
|
+
name=real_fn.__name__,
|
|
67
|
+
entrypoint=code_obj.co_filename,
|
|
68
|
+
)
|
|
69
|
+
SailfishHandlerPatch._sent.add(key)
|
|
70
|
+
|
|
71
|
+
return original_prepare(self, *args, **kwargs)
|
|
72
|
+
|
|
73
|
+
tornado.web.RequestHandler.prepare = patched_prepare
|
|
74
|
+
|
|
75
|
+
# --------------------------------------------------------------- #
|
|
76
|
+
# b) Exception capture – patch _execute and write_error
|
|
77
|
+
# --------------------------------------------------------------- #
|
|
78
|
+
original_execute = tornado.web.RequestHandler._execute
|
|
79
|
+
original_write_error = tornado.web.RequestHandler.write_error
|
|
80
|
+
|
|
81
|
+
async def patched_execute(self, *args, **kwargs):
|
|
82
|
+
try:
|
|
83
|
+
return await original_execute(self, *args, **kwargs)
|
|
84
|
+
except Exception as exc: # HTTPError included
|
|
85
|
+
custom_excepthook(type(exc), exc, exc.__traceback__)
|
|
86
|
+
raise # let Tornado handle 500/4xx
|
|
87
|
+
|
|
88
|
+
def patched_write_error(self, status_code, **kwargs):
|
|
89
|
+
"""
|
|
90
|
+
Tornado calls write_error for HTTPError and uncaught exceptions.
|
|
91
|
+
Capture the exception (when provided) before rendering.
|
|
92
|
+
"""
|
|
93
|
+
exc_info = kwargs.get("exc_info")
|
|
94
|
+
if exc_info and isinstance(exc_info, tuple) and exc_info[1]:
|
|
95
|
+
exc_type, exc_val, exc_tb = exc_info
|
|
96
|
+
custom_excepthook(exc_type, exc_val, exc_tb)
|
|
97
|
+
# Fallback: still call original renderer
|
|
98
|
+
return original_write_error(self, status_code, **kwargs)
|
|
99
|
+
|
|
100
|
+
tornado.web.RequestHandler._execute = patched_execute
|
|
101
|
+
tornado.web.RequestHandler.write_error = patched_write_error
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class SailfishHandlerPatch:
|
|
105
|
+
"""
|
|
106
|
+
Helper to hold our per-request dedupe set.
|
|
107
|
+
We clear this once per IOLoop iteration via on_finish().
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
_sent: Set[Tuple[str, int]] = set()
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
def clear_sent():
|
|
114
|
+
SailfishHandlerPatch._sent.clear()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# Hook into on_finish to reset dedupe for the next request
|
|
118
|
+
try:
|
|
119
|
+
import tornado.web
|
|
120
|
+
|
|
121
|
+
original_on_finish = tornado.web.RequestHandler.on_finish
|
|
122
|
+
|
|
123
|
+
def patched_on_finish(self):
|
|
124
|
+
SailfishHandlerPatch.clear_sent()
|
|
125
|
+
return original_on_finish(self)
|
|
126
|
+
|
|
127
|
+
tornado.web.RequestHandler.on_finish = patched_on_finish
|
|
128
|
+
except ImportError:
|
|
129
|
+
pass
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import sysconfig
|
|
3
|
+
from typing import Any, Callable, Optional, Set
|
|
4
|
+
|
|
5
|
+
_stdlib = sysconfig.get_paths()["stdlib"]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
_ATTR_CANDIDATES = (
|
|
9
|
+
"resolver",
|
|
10
|
+
"func",
|
|
11
|
+
"python_func",
|
|
12
|
+
"_resolver",
|
|
13
|
+
"wrapped_func",
|
|
14
|
+
"__func",
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _is_user_code(path: Optional[str] = None) -> bool:
|
|
19
|
+
return (
|
|
20
|
+
bool(path)
|
|
21
|
+
and not path.startswith(_stdlib)
|
|
22
|
+
and "site-packages" not in path
|
|
23
|
+
and "dist-packages" not in path
|
|
24
|
+
and not path.startswith("<")
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _unwrap_user_func(fn: Callable[..., Any]) -> Callable[..., Any]:
|
|
29
|
+
"""Unwrap decorators & closures until we find your user function."""
|
|
30
|
+
seen: Set[int] = set()
|
|
31
|
+
queue = [fn]
|
|
32
|
+
while queue:
|
|
33
|
+
current = queue.pop()
|
|
34
|
+
if id(current) in seen:
|
|
35
|
+
continue
|
|
36
|
+
seen.add(id(current))
|
|
37
|
+
|
|
38
|
+
if inspect.isfunction(current) and _is_user_code(current.__code__.co_filename):
|
|
39
|
+
return current
|
|
40
|
+
|
|
41
|
+
inner = getattr(current, "__wrapped__", None)
|
|
42
|
+
if inner:
|
|
43
|
+
queue.append(inner)
|
|
44
|
+
|
|
45
|
+
for attr in _ATTR_CANDIDATES:
|
|
46
|
+
attr_val = getattr(current, attr, None)
|
|
47
|
+
if inspect.isfunction(attr_val):
|
|
48
|
+
queue.append(attr_val)
|
|
49
|
+
|
|
50
|
+
for cell in getattr(current, "__closure__", []) or []:
|
|
51
|
+
cc = cell.cell_contents
|
|
52
|
+
if inspect.isfunction(cc):
|
|
53
|
+
queue.append(cc)
|
|
54
|
+
|
|
55
|
+
return fn # fallback
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import builtins
|
|
2
|
+
|
|
3
|
+
from .custom_print import SF_DEBUG, custom_print
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def override_print():
|
|
7
|
+
if hasattr(builtins, "_original_print"):
|
|
8
|
+
return
|
|
9
|
+
# Save the original print function
|
|
10
|
+
builtins._original_print = builtins.print
|
|
11
|
+
|
|
12
|
+
# Override the built-in print function
|
|
13
|
+
builtins.print = custom_print
|