sf-veritas 0.11.10__cp314-cp314-manylinux_2_28_x86_64.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.
Files changed (141) hide show
  1. sf_veritas/__init__.py +46 -0
  2. sf_veritas/_auto_preload.py +73 -0
  3. sf_veritas/_sfconfig.c +162 -0
  4. sf_veritas/_sfconfig.cpython-314-x86_64-linux-gnu.so +0 -0
  5. sf_veritas/_sfcrashhandler.c +267 -0
  6. sf_veritas/_sfcrashhandler.cpython-314-x86_64-linux-gnu.so +0 -0
  7. sf_veritas/_sffastlog.c +953 -0
  8. sf_veritas/_sffastlog.cpython-314-x86_64-linux-gnu.so +0 -0
  9. sf_veritas/_sffastnet.c +994 -0
  10. sf_veritas/_sffastnet.cpython-314-x86_64-linux-gnu.so +0 -0
  11. sf_veritas/_sffastnetworkrequest.c +727 -0
  12. sf_veritas/_sffastnetworkrequest.cpython-314-x86_64-linux-gnu.so +0 -0
  13. sf_veritas/_sffuncspan.c +2791 -0
  14. sf_veritas/_sffuncspan.cpython-314-x86_64-linux-gnu.so +0 -0
  15. sf_veritas/_sffuncspan_config.c +730 -0
  16. sf_veritas/_sffuncspan_config.cpython-314-x86_64-linux-gnu.so +0 -0
  17. sf_veritas/_sfheadercheck.c +341 -0
  18. sf_veritas/_sfheadercheck.cpython-314-x86_64-linux-gnu.so +0 -0
  19. sf_veritas/_sfnetworkhop.c +1454 -0
  20. sf_veritas/_sfnetworkhop.cpython-314-x86_64-linux-gnu.so +0 -0
  21. sf_veritas/_sfservice.c +1223 -0
  22. sf_veritas/_sfservice.cpython-314-x86_64-linux-gnu.so +0 -0
  23. sf_veritas/_sfteepreload.c +6227 -0
  24. sf_veritas/app_config.py +57 -0
  25. sf_veritas/cli.py +336 -0
  26. sf_veritas/constants.py +10 -0
  27. sf_veritas/custom_excepthook.py +304 -0
  28. sf_veritas/custom_log_handler.py +146 -0
  29. sf_veritas/custom_output_wrapper.py +153 -0
  30. sf_veritas/custom_print.py +153 -0
  31. sf_veritas/django_app.py +5 -0
  32. sf_veritas/env_vars.py +186 -0
  33. sf_veritas/exception_handling_middleware.py +18 -0
  34. sf_veritas/exception_metaclass.py +69 -0
  35. sf_veritas/fast_frame_info.py +116 -0
  36. sf_veritas/fast_network_hop.py +293 -0
  37. sf_veritas/frame_tools.py +112 -0
  38. sf_veritas/funcspan_config_loader.py +693 -0
  39. sf_veritas/function_span_profiler.py +1313 -0
  40. sf_veritas/get_preload_path.py +34 -0
  41. sf_veritas/import_hook.py +62 -0
  42. sf_veritas/infra_details/__init__.py +3 -0
  43. sf_veritas/infra_details/get_infra_details.py +24 -0
  44. sf_veritas/infra_details/kubernetes/__init__.py +3 -0
  45. sf_veritas/infra_details/kubernetes/get_cluster_name.py +147 -0
  46. sf_veritas/infra_details/kubernetes/get_details.py +7 -0
  47. sf_veritas/infra_details/running_on/__init__.py +17 -0
  48. sf_veritas/infra_details/running_on/kubernetes.py +11 -0
  49. sf_veritas/interceptors.py +543 -0
  50. sf_veritas/libsfnettee.so +0 -0
  51. sf_veritas/local_env_detect.py +118 -0
  52. sf_veritas/package_metadata.py +6 -0
  53. sf_veritas/patches/__init__.py +0 -0
  54. sf_veritas/patches/_patch_tracker.py +74 -0
  55. sf_veritas/patches/concurrent_futures.py +19 -0
  56. sf_veritas/patches/constants.py +1 -0
  57. sf_veritas/patches/exceptions.py +82 -0
  58. sf_veritas/patches/multiprocessing.py +32 -0
  59. sf_veritas/patches/network_libraries/__init__.py +99 -0
  60. sf_veritas/patches/network_libraries/aiohttp.py +294 -0
  61. sf_veritas/patches/network_libraries/curl_cffi.py +363 -0
  62. sf_veritas/patches/network_libraries/http_client.py +670 -0
  63. sf_veritas/patches/network_libraries/httpcore.py +580 -0
  64. sf_veritas/patches/network_libraries/httplib2.py +315 -0
  65. sf_veritas/patches/network_libraries/httpx.py +557 -0
  66. sf_veritas/patches/network_libraries/niquests.py +218 -0
  67. sf_veritas/patches/network_libraries/pycurl.py +399 -0
  68. sf_veritas/patches/network_libraries/requests.py +595 -0
  69. sf_veritas/patches/network_libraries/ssl_socket.py +822 -0
  70. sf_veritas/patches/network_libraries/tornado.py +360 -0
  71. sf_veritas/patches/network_libraries/treq.py +270 -0
  72. sf_veritas/patches/network_libraries/urllib_request.py +483 -0
  73. sf_veritas/patches/network_libraries/utils.py +598 -0
  74. sf_veritas/patches/os.py +17 -0
  75. sf_veritas/patches/threading.py +231 -0
  76. sf_veritas/patches/web_frameworks/__init__.py +54 -0
  77. sf_veritas/patches/web_frameworks/aiohttp.py +798 -0
  78. sf_veritas/patches/web_frameworks/async_websocket_consumer.py +337 -0
  79. sf_veritas/patches/web_frameworks/blacksheep.py +532 -0
  80. sf_veritas/patches/web_frameworks/bottle.py +513 -0
  81. sf_veritas/patches/web_frameworks/cherrypy.py +683 -0
  82. sf_veritas/patches/web_frameworks/cors_utils.py +122 -0
  83. sf_veritas/patches/web_frameworks/django.py +963 -0
  84. sf_veritas/patches/web_frameworks/eve.py +401 -0
  85. sf_veritas/patches/web_frameworks/falcon.py +931 -0
  86. sf_veritas/patches/web_frameworks/fastapi.py +738 -0
  87. sf_veritas/patches/web_frameworks/flask.py +526 -0
  88. sf_veritas/patches/web_frameworks/klein.py +501 -0
  89. sf_veritas/patches/web_frameworks/litestar.py +616 -0
  90. sf_veritas/patches/web_frameworks/pyramid.py +440 -0
  91. sf_veritas/patches/web_frameworks/quart.py +841 -0
  92. sf_veritas/patches/web_frameworks/robyn.py +708 -0
  93. sf_veritas/patches/web_frameworks/sanic.py +874 -0
  94. sf_veritas/patches/web_frameworks/starlette.py +742 -0
  95. sf_veritas/patches/web_frameworks/strawberry.py +1446 -0
  96. sf_veritas/patches/web_frameworks/tornado.py +485 -0
  97. sf_veritas/patches/web_frameworks/utils.py +170 -0
  98. sf_veritas/print_override.py +13 -0
  99. sf_veritas/regular_data_transmitter.py +444 -0
  100. sf_veritas/request_interceptor.py +401 -0
  101. sf_veritas/request_utils.py +550 -0
  102. sf_veritas/segfault_handler.py +116 -0
  103. sf_veritas/server_status.py +1 -0
  104. sf_veritas/shutdown_flag.py +11 -0
  105. sf_veritas/subprocess_startup.py +3 -0
  106. sf_veritas/test_cli.py +145 -0
  107. sf_veritas/thread_local.py +1319 -0
  108. sf_veritas/timeutil.py +114 -0
  109. sf_veritas/transmit_exception_to_sailfish.py +28 -0
  110. sf_veritas/transmitter.py +132 -0
  111. sf_veritas/types.py +47 -0
  112. sf_veritas/unified_interceptor.py +1678 -0
  113. sf_veritas/utils.py +39 -0
  114. sf_veritas-0.11.10.dist-info/METADATA +97 -0
  115. sf_veritas-0.11.10.dist-info/RECORD +141 -0
  116. sf_veritas-0.11.10.dist-info/WHEEL +5 -0
  117. sf_veritas-0.11.10.dist-info/entry_points.txt +2 -0
  118. sf_veritas-0.11.10.dist-info/top_level.txt +1 -0
  119. sf_veritas.libs/libbrotlicommon-6ce2a53c.so.1.0.6 +0 -0
  120. sf_veritas.libs/libbrotlidec-811d1be3.so.1.0.6 +0 -0
  121. sf_veritas.libs/libcom_err-730ca923.so.2.1 +0 -0
  122. sf_veritas.libs/libcrypt-52aca757.so.1.1.0 +0 -0
  123. sf_veritas.libs/libcrypto-bdaed0ea.so.1.1.1k +0 -0
  124. sf_veritas.libs/libcurl-eaa3cf66.so.4.5.0 +0 -0
  125. sf_veritas.libs/libgssapi_krb5-323bbd21.so.2.2 +0 -0
  126. sf_veritas.libs/libidn2-2f4a5893.so.0.3.6 +0 -0
  127. sf_veritas.libs/libk5crypto-9a74ff38.so.3.1 +0 -0
  128. sf_veritas.libs/libkeyutils-2777d33d.so.1.6 +0 -0
  129. sf_veritas.libs/libkrb5-a55300e8.so.3.3 +0 -0
  130. sf_veritas.libs/libkrb5support-e6594cfc.so.0.1 +0 -0
  131. sf_veritas.libs/liblber-2-d20824ef.4.so.2.10.9 +0 -0
  132. sf_veritas.libs/libldap-2-cea2a960.4.so.2.10.9 +0 -0
  133. sf_veritas.libs/libnghttp2-39367a22.so.14.17.0 +0 -0
  134. sf_veritas.libs/libpcre2-8-516f4c9d.so.0.7.1 +0 -0
  135. sf_veritas.libs/libpsl-99becdd3.so.5.3.1 +0 -0
  136. sf_veritas.libs/libsasl2-7de4d792.so.3.0.0 +0 -0
  137. sf_veritas.libs/libselinux-d0805dcb.so.1 +0 -0
  138. sf_veritas.libs/libssh-c11d285b.so.4.8.7 +0 -0
  139. sf_veritas.libs/libssl-60250281.so.1.1.1k +0 -0
  140. sf_veritas.libs/libunistring-05abdd40.so.2.1.0 +0 -0
  141. sf_veritas.libs/libuuid-95b83d40.so.1.3.0 +0 -0
@@ -0,0 +1,1446 @@
1
+ import functools
2
+ import json
3
+ import inspect
4
+ import logging
5
+ import os
6
+ import random
7
+ import sys
8
+ import threading
9
+ import traceback
10
+ from importlib.util import find_spec
11
+ from typing import Any, Callable, Set, Tuple
12
+
13
+ from ... import _sffuncspan
14
+ from ... import app_config
15
+ from ...custom_excepthook import custom_excepthook
16
+ from ...env_vars import (
17
+ CAPTURE_STRAWBERRY_ERRORS_WITH_DATA,
18
+ PRINT_CONFIGURATION_STATUSES,
19
+ SF_DEBUG,
20
+ SF_FUNCSPAN_CAPTURE_INSTALLED_PACKAGES,
21
+ SF_NETWORKHOP_CAPTURE_REQUEST_BODY,
22
+ SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS,
23
+ SF_NETWORKHOP_CAPTURE_RESPONSE_BODY,
24
+ SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS,
25
+ SF_NETWORKHOP_REQUEST_LIMIT_MB,
26
+ SF_NETWORKHOP_RESPONSE_LIMIT_MB,
27
+ STRAWBERRY_DEBUG,
28
+ )
29
+ from ...fast_network_hop import (
30
+ fast_send_network_hop,
31
+ fast_send_network_hop_fast,
32
+ register_endpoint,
33
+ )
34
+ from ...function_span_profiler import _HAS_NATIVE
35
+ from ...thread_local import get_or_set_sf_trace_id, get_current_function_span_id
36
+ from ...transmit_exception_to_sailfish import transmit_exception_to_sailfish
37
+ from .utils import _is_user_code, _unwrap_user_func
38
+
39
+ # JSON serialization - try fast orjson first, fallback to stdlib json
40
+ try:
41
+ import orjson
42
+
43
+ HAS_ORJSON = True
44
+ except ImportError:
45
+ import json
46
+
47
+
48
+ logger = logging.getLogger(__name__)
49
+
50
+ # Size limits in bytes
51
+ _REQUEST_LIMIT_BYTES = SF_NETWORKHOP_REQUEST_LIMIT_MB * 1024 * 1024
52
+ _RESPONSE_LIMIT_BYTES = SF_NETWORKHOP_RESPONSE_LIMIT_MB * 1024 * 1024
53
+
54
+ # Pre-registered endpoint IDs
55
+ _ENDPOINT_REGISTRY: dict[tuple, int] = {}
56
+
57
+ # Track if Strawberry has already been patched to prevent multiple patches
58
+ _is_strawberry_patched = False
59
+
60
+ # Thread-local tracker for preventing double invocation of resolve methods
61
+ _sf_processing_tracker = threading.local()
62
+
63
+ # Cache for function definition line numbers (keyed by code object id)
64
+ _FUNCTION_DEF_LINE_CACHE: dict[int, int] = {}
65
+
66
+
67
+ def _get_function_def_line(frame):
68
+ """
69
+ Get the line number of the 'def' statement, skipping decorators.
70
+
71
+ Python's co_firstlineno includes decorators, so we need to scan the source
72
+ to find the actual function definition line.
73
+
74
+ PERFORMANCE: Results are cached by code object ID, so the file I/O only
75
+ happens once per function (first request). Subsequent requests are instant.
76
+ """
77
+ code_id = id(frame.f_code)
78
+
79
+ # Check cache first - this is the fast path for all requests after the first
80
+ if code_id in _FUNCTION_DEF_LINE_CACHE:
81
+ return _FUNCTION_DEF_LINE_CACHE[code_id]
82
+
83
+ # Cache miss - do the expensive source file lookup
84
+ try:
85
+ # Get source lines for this code object (SLOW: reads file from disk)
86
+ source_lines, start_line = inspect.getsourcelines(frame.f_code)
87
+
88
+ # Find the first line that starts with 'def' or 'async def'
89
+ for i, line in enumerate(source_lines):
90
+ stripped = line.strip()
91
+ if stripped.startswith("def ") or stripped.startswith("async def "):
92
+ def_line = start_line + i
93
+ # Cache the result for next time
94
+ _FUNCTION_DEF_LINE_CACHE[code_id] = def_line
95
+ return def_line
96
+
97
+ # Fallback: return co_firstlineno if we can't find def
98
+ result = frame.f_code.co_firstlineno
99
+ _FUNCTION_DEF_LINE_CACHE[code_id] = result
100
+ return result
101
+ except Exception:
102
+ # If anything fails, fallback to co_firstlineno
103
+ result = frame.f_code.co_firstlineno
104
+ _FUNCTION_DEF_LINE_CACHE[code_id] = result
105
+ return result
106
+
107
+
108
+ def get_extension():
109
+ from strawberry.extensions import SchemaExtension
110
+
111
+ class CustomErrorHandlingExtension(SchemaExtension):
112
+ def __init__(self, *, execution_context):
113
+ self.execution_context = execution_context
114
+
115
+ def on_request_start(self):
116
+ if SF_DEBUG and app_config._interceptors_initialized:
117
+ print("Starting GraphQL request", log=False)
118
+
119
+ def on_request_end(self):
120
+ if SF_DEBUG and app_config._interceptors_initialized:
121
+ print("Ending GraphQL request", log=False)
122
+ if not self.execution_context.errors:
123
+ return
124
+ for error in self.execution_context.errors:
125
+ if SF_DEBUG and app_config._interceptors_initialized:
126
+ print(f"Handling GraphQL error: {error}", log=False)
127
+ custom_excepthook(type(error), error, error.__traceback__)
128
+
129
+ def on_validation_start(self):
130
+ if SF_DEBUG and app_config._interceptors_initialized:
131
+ print("Starting validation of GraphQL request", log=False)
132
+
133
+ def on_validation_end(self):
134
+ if SF_DEBUG and app_config._interceptors_initialized:
135
+ print("Ending validation of GraphQL request", log=False)
136
+
137
+ def on_execution_start(self):
138
+ if SF_DEBUG and app_config._interceptors_initialized:
139
+ print("Starting execution of GraphQL request", log=False)
140
+
141
+ def on_resolver_start(self, resolver, obj, info, **kwargs):
142
+ if SF_DEBUG and app_config._interceptors_initialized:
143
+ print(f"Starting resolver {resolver.__name__}", log=False)
144
+
145
+ def on_resolver_end(self, resolver, obj, info, **kwargs):
146
+ if SF_DEBUG and app_config._interceptors_initialized:
147
+ print(f"Ending resolver {resolver.__name__}", log=False)
148
+
149
+ def on_error(self, error: Exception):
150
+ if SF_DEBUG and app_config._interceptors_initialized:
151
+ print(f"Handling error in resolver: {error}", log=False)
152
+ custom_excepthook(type(error), error, error.__traceback__)
153
+
154
+ return CustomErrorHandlingExtension
155
+
156
+
157
+ def get_network_hop_extension() -> "type[SchemaExtension]":
158
+ """
159
+ Strawberry SchemaExtension that emits a collectNetworkHops mutation for the
160
+ *first* user-land frame executed inside every resolver (sync or async).
161
+ """
162
+
163
+ from strawberry.extensions import SchemaExtension
164
+
165
+ # --------------------------------------------------------------------- #
166
+ # Helper predicates
167
+ # --------------------------------------------------------------------- #
168
+ # Extended dig: __wrapped__, closure cells *and* common attribute names
169
+ # --------------------------------------------------------------------- #
170
+ # Extension class
171
+ # --------------------------------------------------------------------- #
172
+ class NetworkHopExtension(SchemaExtension):
173
+ supports_sync = supports_async = True
174
+ _sent: Set[Tuple[str, int]] = set() # class-level: de-dupe per request
175
+
176
+ def __init__(self, *, execution_context):
177
+ super().__init__(execution_context=execution_context)
178
+ self._captured_endpoints = (
179
+ []
180
+ ) # Store endpoint info for post-response emission
181
+ self._request_data = {} # Store request headers/body
182
+ self._response_data = {} # Store response headers/body
183
+
184
+ # ---------------- internal capture helper ---------------- #
185
+ def _capture(self, frame, info):
186
+ """OTEL-STYLE: Capture endpoint metadata and pre-register."""
187
+ filename = frame.f_code.co_filename
188
+ func_name = frame.f_code.co_name
189
+
190
+ # Get the actual function definition line (skipping decorators)
191
+ line_no = _get_function_def_line(frame)
192
+
193
+ if SF_DEBUG and app_config._interceptors_initialized:
194
+ print(
195
+ f"[[Strawberry]] _capture: {func_name} @ {filename} "
196
+ f"co_firstlineno={frame.f_code.co_firstlineno} -> def_line={line_no}",
197
+ log=False,
198
+ )
199
+ if (filename, line_no) in NetworkHopExtension._sent:
200
+ return
201
+
202
+ hop_key = (filename, line_no)
203
+
204
+ # Pre-register endpoint if not already registered
205
+ endpoint_id = _ENDPOINT_REGISTRY.get(hop_key)
206
+ if endpoint_id is None:
207
+ endpoint_id = register_endpoint(
208
+ line=str(line_no),
209
+ column="0",
210
+ name=func_name,
211
+ entrypoint=filename,
212
+ route=None,
213
+ )
214
+ if endpoint_id >= 0:
215
+ _ENDPOINT_REGISTRY[hop_key] = endpoint_id
216
+ if SF_DEBUG and app_config._interceptors_initialized:
217
+ print(
218
+ f"[[Strawberry]] Registered resolver: {func_name} @ "
219
+ f"{filename}:{line_no} (id={endpoint_id})",
220
+ log=False,
221
+ )
222
+
223
+ # Store for on_request_end to emit
224
+ self._captured_endpoints.append(
225
+ {
226
+ "filename": filename,
227
+ "line": line_no,
228
+ "name": func_name,
229
+ "endpoint_id": endpoint_id,
230
+ }
231
+ )
232
+ NetworkHopExtension._sent.add((filename, line_no))
233
+
234
+ if SF_DEBUG and app_config._interceptors_initialized:
235
+ print(
236
+ f"[[Strawberry]] Captured resolver: {func_name} "
237
+ f"({filename}:{line_no}) endpoint_id={endpoint_id}",
238
+ log=False,
239
+ )
240
+
241
+ # ---------------- tracer factory ---------------- #
242
+ def _make_tracer(self, info):
243
+ def tracer(frame, event, arg):
244
+ if event.startswith("c_"):
245
+ return
246
+ if event == "call":
247
+ if _is_user_code(frame.f_code.co_filename):
248
+ self._capture(frame, info)
249
+ sys.setprofile(None)
250
+ return
251
+ return tracer # keep tracing until we hit user code
252
+
253
+ return tracer
254
+
255
+ # ---------------- request/response capture ---------------- #
256
+ def on_request_start(self):
257
+ """Capture GraphQL request data when request starts."""
258
+ # IMPORTANT: Clear captured endpoints from previous requests
259
+ # SchemaExtension instances may be reused across requests
260
+ self._captured_endpoints = []
261
+ self._request_data = {}
262
+ self._response_data = {}
263
+
264
+ if (
265
+ SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS
266
+ or SF_NETWORKHOP_CAPTURE_REQUEST_BODY
267
+ ):
268
+ try:
269
+ # Access the GraphQL query from execution context
270
+ if (
271
+ SF_NETWORKHOP_CAPTURE_REQUEST_BODY
272
+ and self.execution_context.query
273
+ ):
274
+
275
+ query_data = {
276
+ "query": self.execution_context.query,
277
+ "variables": self.execution_context.variables or {},
278
+ "operation_name": self.execution_context.operation_name,
279
+ }
280
+ # Convert to JSON string and limit size
281
+ if HAS_ORJSON:
282
+ query_str = orjson.dumps(query_data)[:_REQUEST_LIMIT_BYTES]
283
+ else:
284
+ query_str = json.dumps(query_data)[:_REQUEST_LIMIT_BYTES]
285
+ self._request_data["body"] = query_str.encode("utf-8")
286
+ if SF_DEBUG and app_config._interceptors_initialized:
287
+ print(
288
+ f"[[Strawberry]] Captured GraphQL query: {len(query_str)} chars",
289
+ log=False,
290
+ )
291
+
292
+ # Try to capture HTTP headers if available (depends on integration)
293
+ if SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS:
294
+ # For Django/Flask integrations, headers might be in context
295
+ if hasattr(self.execution_context, "context"):
296
+ ctx = self.execution_context.context
297
+ if hasattr(ctx, "request") and hasattr(
298
+ ctx.request, "headers"
299
+ ):
300
+ self._request_data["headers"] = dict(
301
+ ctx.request.headers
302
+ )
303
+ if SF_DEBUG and app_config._interceptors_initialized:
304
+ print(
305
+ f"[[Strawberry]] Captured {len(self._request_data['headers'])} request headers",
306
+ log=False,
307
+ )
308
+ except Exception as e:
309
+ if SF_DEBUG and app_config._interceptors_initialized:
310
+ print(
311
+ f"[[Strawberry]] Failed to capture request data: {e}",
312
+ log=False,
313
+ )
314
+
315
+ # ---------------- wrappers ---------------- #
316
+ def resolve(self, _next, root, info, *args, **kwargs):
317
+ # CRITICAL: Prevent double-invocation
318
+ # Strawberry calls each extension's resolve method multiple times in the chain
319
+ # We only want to process once per actual GraphQL field resolution
320
+ # Use a thread-local tracking dict since GraphQLResolveInfo might not allow attribute assignment
321
+ if not hasattr(_sf_processing_tracker, 'processing'):
322
+ _sf_processing_tracker.processing = {}
323
+
324
+ field_key = (id(info), info.field_name if hasattr(info, 'field_name') else None)
325
+
326
+ # Check if we're already processing this field
327
+ if field_key in _sf_processing_tracker.processing:
328
+ # Already processing this field, just pass through to next extension
329
+ return _next(root, info, *args, **kwargs)
330
+
331
+ # Mark this field as being processed
332
+ _sf_processing_tracker.processing[field_key] = True
333
+
334
+ try:
335
+ # Get the actual resolver from info.field_name
336
+ # _next is another extension in the chain, not the actual user resolver!
337
+ resolver_func = None
338
+ func_name = "<unknown>"
339
+ filename = "<unknown>"
340
+ line_no = 0
341
+
342
+ # Try multiple approaches to get the actual resolver function
343
+ # Strawberry stores resolvers in different places depending on the field type
344
+
345
+ # Approach 1: Check if info has python_name (Strawberry field attribute)
346
+ if hasattr(info, 'python_name') and hasattr(root, info.python_name):
347
+ attr = getattr(root, info.python_name, None)
348
+ if callable(attr):
349
+ resolver_func = attr
350
+ if SF_DEBUG and app_config._interceptors_initialized:
351
+ print(f"[[Strawberry]] Got resolver from root.{info.python_name}: {resolver_func}", log=False)
352
+
353
+ # Approach 2: Try _field.base_resolver (works for root queries where root=None)
354
+ if not resolver_func and hasattr(info, '_field'):
355
+ # For root queries, info._field should have the resolver
356
+ field_obj = info._field
357
+ # Try base_resolver first
358
+ if hasattr(field_obj, 'base_resolver') and field_obj.base_resolver:
359
+ resolver_func = field_obj.base_resolver
360
+
361
+ # If base_resolver is None, try origin (Strawberry stores the original function here)
362
+ elif hasattr(field_obj, 'origin') and callable(field_obj.origin):
363
+ resolver_func = field_obj.origin
364
+
365
+ # Approach 3: Try parent_type and field_name to look up the resolver
366
+ if not resolver_func and hasattr(info, 'parent_type') and hasattr(info, 'field_name'):
367
+ # Get the Strawberry type definition from the parent
368
+ parent_type_name = info.parent_type.name if hasattr(info.parent_type, 'name') else None
369
+ if parent_type_name and hasattr(root, '__class__'):
370
+ # Try to get the method from the root object's class
371
+ method = getattr(root.__class__, info.field_name, None)
372
+ if method and callable(method):
373
+ resolver_func = method
374
+
375
+ # Approach 4: Try to get resolver directly from the field definition
376
+ if not resolver_func and hasattr(info, '_field'):
377
+ field = info._field
378
+ # Try multiple attributes where Strawberry might store the resolver
379
+ for attr_name in ['base_resolver', 'resolver', 'wrapped_func', 'python_name']:
380
+ if hasattr(field, attr_name):
381
+ candidate = getattr(field, attr_name)
382
+ if callable(candidate):
383
+ resolver_func = candidate
384
+ break
385
+
386
+ # Approach 5: Get resolver from GraphQL field definition (when _field is not available)
387
+ if not resolver_func and hasattr(info, 'parent_type') and hasattr(info, 'field_name'):
388
+ # Access the GraphQL field from the parent type
389
+ try:
390
+ graphql_field = info.parent_type.fields.get(info.field_name)
391
+ if graphql_field and hasattr(graphql_field, 'resolve'):
392
+ resolver_func = graphql_field.resolve
393
+ except Exception as e:
394
+ pass
395
+
396
+ # Fallback: just use field_name
397
+ if not resolver_func and hasattr(info, 'field_name'):
398
+ func_name = info.field_name
399
+
400
+ # If we found a resolver function, extract its details
401
+ if resolver_func:
402
+ # Unwrap decorators and get the actual function
403
+ actual_func = resolver_func
404
+ # Handle bound methods
405
+ if hasattr(actual_func, '__func__'):
406
+ actual_func = actual_func.__func__
407
+ # Handle wrapped functions
408
+ while hasattr(actual_func, '__wrapped__'):
409
+ actual_func = actual_func.__wrapped__
410
+
411
+ # CRITICAL: If we got Strawberry's _async_resolver wrapper, try to extract the real user function
412
+ # Strawberry wraps user resolvers in multiple layers:
413
+ # _async_resolver -> extension_resolver -> actual user function
414
+ # We need to recursively unwrap to find the actual user function
415
+ def unwrap_strawberry_resolver(func, depth=0, max_depth=5):
416
+ """Recursively unwrap Strawberry resolver wrappers to find the user function."""
417
+ if depth >= max_depth:
418
+ return None
419
+
420
+ if not (hasattr(func, '__code__') and hasattr(func, '__closure__')):
421
+ return None
422
+
423
+ # Check if this is a Strawberry wrapper function
424
+ is_strawberry = 'site-packages/strawberry' in func.__code__.co_filename
425
+ if not is_strawberry:
426
+ # Found a non-Strawberry function!
427
+ return func
428
+
429
+ # Look through closure for wrapped function
430
+ if func.__closure__:
431
+ for idx, cell in enumerate(func.__closure__):
432
+ try:
433
+ cell_content = cell.cell_contents
434
+
435
+ if callable(cell_content) and hasattr(cell_content, '__code__'):
436
+ cell_filename = cell_content.__code__.co_filename
437
+ cell_funcname = cell_content.__name__
438
+
439
+ # Check if this is user code
440
+ is_user_code = 'site-packages' not in cell_filename and 'dist-packages' not in cell_filename
441
+ if is_user_code:
442
+ # Found user function!
443
+ return cell_content
444
+
445
+ # Recursively check this function's closure
446
+ result = unwrap_strawberry_resolver(cell_content, depth + 1, max_depth)
447
+ if result:
448
+ return result
449
+
450
+ # Also check if the cell content has a 'base_resolver' attribute (StrawberryField)
451
+ # and try to extract the actual resolver function from it
452
+ if hasattr(cell_content, 'base_resolver'):
453
+ base_res = cell_content.base_resolver
454
+ if base_res and callable(base_res):
455
+ # If base_resolver is user code, return it directly
456
+ if hasattr(base_res, '__code__'):
457
+ resolver_file = base_res.__code__.co_filename
458
+ resolver_name = base_res.__name__
459
+ if 'site-packages' not in resolver_file and 'dist-packages' not in resolver_file:
460
+ return base_res
461
+ # StrawberryResolver object - extract the wrapped function
462
+ # Try common attributes where Strawberry stores the actual function
463
+ for attr in ['wrapped_func', '_wrapped_func', 'func', '_func', '__wrapped__']:
464
+ if hasattr(base_res, attr):
465
+ wrapped = getattr(base_res, attr)
466
+ if wrapped and callable(wrapped) and hasattr(wrapped, '__code__'):
467
+ resolver_file = wrapped.__code__.co_filename
468
+ resolver_name = wrapped.__name__
469
+ if 'site-packages' not in resolver_file and 'dist-packages' not in resolver_file:
470
+ return wrapped
471
+ # Otherwise recursively unwrap it
472
+ result = unwrap_strawberry_resolver(base_res, depth + 1, max_depth)
473
+ if result:
474
+ return result
475
+
476
+ except (AttributeError, ValueError):
477
+ pass
478
+
479
+ return None
480
+
481
+ if hasattr(actual_func, '__code__') and actual_func.__name__ == '_async_resolver':
482
+ if 'site-packages/strawberry' in actual_func.__code__.co_filename:
483
+ unwrapped = unwrap_strawberry_resolver(actual_func)
484
+ if unwrapped:
485
+ actual_func = unwrapped
486
+
487
+ if hasattr(actual_func, '__code__'):
488
+ filename = actual_func.__code__.co_filename
489
+ func_name = actual_func.__name__
490
+ line_no = actual_func.__code__.co_firstlineno
491
+
492
+ # CRITICAL: Skip profiling telemetry collection resolvers to prevent infinite loops
493
+ # When function spans are sent to Django's GraphQL endpoint, they trigger these resolvers
494
+ # If we profile them, we create MORE function spans, which trigger MORE resolver calls, etc.
495
+ _TELEMETRY_RESOLVERS_TO_SKIP = {
496
+ 'collectFunctionSpans', 'collect_function_spans',
497
+ 'collectNetworkRequest', 'collect_network_request',
498
+ 'collectNetworkHops', 'collect_network_hops',
499
+ 'collectLogs', 'collect_logs',
500
+ 'collectPrintStatements', 'collect_print_statements',
501
+ 'collectExceptions', 'collect_exceptions',
502
+ 'collectConsoleLogs', 'collect_console_logs',
503
+ }
504
+
505
+ # Also check the GraphQL field name from info (might differ from Python function name)
506
+ graphql_field_name = info.field_name if hasattr(info, 'field_name') else None
507
+
508
+ # Detect if this is a trivial field resolver (simple property accessor)
509
+ # IMPORTANT: Only skip trivial resolvers if we couldn't extract func details
510
+ # If we successfully extracted filename/line_no, it's a real resolver we should capture
511
+ is_trivial_resolver = False
512
+ if not resolver_func:
513
+ # No resolver function found at all - this is a property accessor
514
+ is_trivial_resolver = True
515
+ elif filename == "<unknown>" or func_name == "<unknown>":
516
+ # Failed to extract details - likely trivial
517
+ is_trivial_resolver = True
518
+ elif resolver_func and hasattr(resolver_func, '__code__'):
519
+ code = resolver_func.__code__
520
+ # Very short bytecode (< 30 bytes) indicates simple property access like "return self.field"
521
+ if code.co_code and len(code.co_code) < 30:
522
+ is_trivial_resolver = True
523
+
524
+ # Check both Python function name and GraphQL field name for telemetry resolvers
525
+ is_telemetry_resolver = (
526
+ func_name in _TELEMETRY_RESOLVERS_TO_SKIP or
527
+ graphql_field_name in _TELEMETRY_RESOLVERS_TO_SKIP
528
+ )
529
+
530
+ # Skip if it's a telemetry resolver or trivial field resolver
531
+ if is_telemetry_resolver or is_trivial_resolver:
532
+ # Skip profiling entirely - no network hops, no function spans
533
+ # This prevents infinite loops when function spans are sent to GraphQL endpoint
534
+ return _next(root, info, *args, **kwargs)
535
+
536
+ # CRITICAL: Skip resolvers from site-packages (unless explicitly enabled)
537
+ # This respects the SF_FUNCSPAN_CAPTURE_INSTALLED_PACKAGES setting
538
+ if not SF_FUNCSPAN_CAPTURE_INSTALLED_PACKAGES and filename != "<unknown>":
539
+ if "site-packages" in filename or "dist-packages" in filename:
540
+ return _next(root, info, *args, **kwargs)
541
+
542
+ # Auto-profile sync GraphQL resolvers with function span capture
543
+ try:
544
+ if _HAS_NATIVE and app_config._interceptors_initialized:
545
+ # Apply sampling BEFORE pushing span - respect per-function sample_rate config
546
+ try:
547
+ func_config = _sffuncspan.get_function_config(filename, func_name)
548
+ sample_rate = func_config.get('sample_rate', 1.0) if func_config else 1.0
549
+ if sample_rate < 1.0 and random.random() >= sample_rate:
550
+ # Sampled out - skip span capture, but still do network hop tracing
551
+ tracer = self._make_tracer(info)
552
+ sys.setprofile(tracer)
553
+ try:
554
+ result = _next(root, info, *args, **kwargs)
555
+ finally:
556
+ sys.setprofile(None)
557
+ return result
558
+ except:
559
+ # If config lookup fails, proceed with capture (default to capturing)
560
+ pass
561
+
562
+ # Generate span ID
563
+ span_id = _sffuncspan.generate_span_id()
564
+ parent_span_id = _sffuncspan.peek_parent_span_id()
565
+
566
+ if SF_DEBUG and app_config._interceptors_initialized:
567
+ print(f"[[Strawberry]] BEFORE PUSH: resolver={func_name}, parent_span_id={parent_span_id} (should be None for top-level resolver)", log=False)
568
+
569
+ # NOTE: We do NOT call cache_config here because:
570
+ # 1. Nested async function capture doesn't work (Python profiler limitation)
571
+ # 2. Hardcoded values would override user configuration
572
+ # 3. Let the function span profiler use its default configuration
573
+
574
+ _sffuncspan.push_span(span_id)
575
+
576
+ if SF_DEBUG and app_config._interceptors_initialized:
577
+ print(f"[[Strawberry]] AFTER PUSH: Pushed span_id={span_id} for resolver={func_name}", log=False)
578
+ # Verify it's in the stack
579
+ current = get_current_function_span_id()
580
+ print(f"[[Strawberry]] AFTER PUSH: get_current_function_span_id()={current}", log=False)
581
+
582
+ # resolver_func, func_name, filename, line_no already set above from info
583
+
584
+ # Capture arguments (lightweight - just resolver name)
585
+ arguments_json = json.dumps({
586
+ "resolver": func_name,
587
+ "root_type": type(root).__name__ if root else None
588
+ })
589
+
590
+ # Record start time
591
+ start_ns = _sffuncspan.get_epoch_ns()
592
+
593
+ # NOTE: C profiler is already running globally (started during app init)
594
+ # We do NOT start/stop it per resolver - just manage the span stack
595
+
596
+ # Execute resolver
597
+ exception_occurred = True # Initialize before try block
598
+ try:
599
+ result = _next(root, info, *args, **kwargs)
600
+ exception_occurred = False
601
+ except Exception as e:
602
+ exception_occurred = True
603
+ result = None
604
+ raise
605
+
606
+ # CRITICAL: Check if result is a coroutine (async resolver)
607
+ # If so, we need to wrap it to keep the span on the stack during execution
608
+ if inspect.iscoroutine(result):
609
+ # Async resolver - wrap the coroutine to maintain span context
610
+ async def wrapped_async_result():
611
+ # Span is still on stack from push above
612
+ # C profiler is already running globally, so it will capture nested calls
613
+ exception_occurred_inner = True # Initialize before try block
614
+ try:
615
+ actual_result = await result
616
+ exception_occurred_inner = False
617
+ return actual_result
618
+ except Exception as e:
619
+ exception_occurred_inner = True
620
+ raise
621
+ finally:
622
+ # Now pop the span after async execution completes
623
+ end_ns = _sffuncspan.get_epoch_ns()
624
+ duration_ns = end_ns - start_ns
625
+ _sffuncspan.pop_span()
626
+
627
+ # Record span
628
+ return_value_json = None
629
+ if not exception_occurred_inner and 'actual_result' in locals():
630
+ try:
631
+ # Use C serializer for full value capture (not just type metadata)
632
+ return_value_json = _sffuncspan.serialize_value(actual_result)
633
+ except:
634
+ return_value_json = None
635
+
636
+ try:
637
+ _, session_id = get_or_set_sf_trace_id()
638
+ _sffuncspan.record_span(
639
+ session_id, span_id, parent_span_id,
640
+ filename, line_no, 0, func_name,
641
+ arguments_json, return_value_json,
642
+ start_ns, duration_ns,
643
+ )
644
+ except Exception as e:
645
+ pass
646
+
647
+ return wrapped_async_result()
648
+
649
+ # Sync resolver - pop span immediately (C profiler stays running)
650
+ end_ns = _sffuncspan.get_epoch_ns()
651
+ duration_ns = end_ns - start_ns
652
+ _sffuncspan.pop_span()
653
+
654
+ # Capture return value using C serializer
655
+ return_value_json = None
656
+ if not exception_occurred:
657
+ try:
658
+ # Use C serializer for full value capture (not just type metadata)
659
+ return_value_json = _sffuncspan.serialize_value(result)
660
+ except:
661
+ return_value_json = None
662
+
663
+ # Record span
664
+ try:
665
+ _, session_id = get_or_set_sf_trace_id()
666
+ _sffuncspan.record_span(
667
+ session_id,
668
+ span_id,
669
+ parent_span_id,
670
+ filename,
671
+ line_no,
672
+ 0, # column
673
+ func_name,
674
+ arguments_json,
675
+ return_value_json,
676
+ start_ns,
677
+ duration_ns,
678
+ )
679
+ except Exception as e:
680
+ if SF_DEBUG and app_config._interceptors_initialized:
681
+ print(f"[[Strawberry]] Failed to record funcspan: {e}", log=False)
682
+
683
+ return result
684
+ else:
685
+ # Fallback: just do network hop tracing
686
+ tracer = self._make_tracer(info)
687
+ sys.setprofile(tracer)
688
+ try:
689
+ return _next(root, info, *args, **kwargs)
690
+ finally:
691
+ sys.setprofile(None)
692
+ except ImportError:
693
+ # sf_funcspan not available, just do network hop tracing
694
+ tracer = self._make_tracer(info)
695
+ sys.setprofile(tracer)
696
+ try:
697
+ return _next(root, info, *args, **kwargs)
698
+ finally:
699
+ sys.setprofile(None) # safety-net
700
+ finally:
701
+ # Clean up tracking flag to prevent memory leaks
702
+ if hasattr(_sf_processing_tracker, 'processing') and field_key in _sf_processing_tracker.processing:
703
+ del _sf_processing_tracker.processing[field_key]
704
+
705
+ async def resolve_async(self, _next, root, info, *args, **kwargs):
706
+ # Get the actual resolver from info.field_name
707
+ # _next is another extension in the chain, not the actual user resolver!
708
+ resolver_func = None
709
+ func_name = "<unknown>"
710
+ filename = "<unknown>"
711
+ line_no = 0
712
+
713
+ # Try multiple approaches to get the actual resolver function
714
+ # Strawberry stores resolvers in different places depending on the field type
715
+
716
+ # Approach 1: Check if info has python_name (Strawberry field attribute)
717
+ if hasattr(info, 'python_name') and hasattr(root, info.python_name):
718
+ attr = getattr(root, info.python_name, None)
719
+ if callable(attr):
720
+ resolver_func = attr
721
+ if SF_DEBUG and app_config._interceptors_initialized:
722
+ print(f"[[Strawberry]] ASYNC Got resolver from root.{info.python_name}: {resolver_func}", log=False)
723
+
724
+ # Approach 2: Try _field.base_resolver
725
+ if not resolver_func and hasattr(info, '_field') and hasattr(info._field, 'base_resolver'):
726
+ resolver_func = info._field.base_resolver
727
+ if SF_DEBUG and app_config._interceptors_initialized:
728
+ print(f"[[Strawberry]] ASYNC Got resolver from info._field.base_resolver: {resolver_func}", log=False)
729
+
730
+ # Approach 3: Try parent_type and field_name to look up the resolver
731
+ if not resolver_func and hasattr(info, 'parent_type') and hasattr(info, 'field_name'):
732
+ # Get the Strawberry type definition from the parent
733
+ parent_type_name = info.parent_type.name if hasattr(info.parent_type, 'name') else None
734
+ if parent_type_name and hasattr(root, '__class__'):
735
+ # Try to get the method from the root object's class
736
+ method = getattr(root.__class__, info.field_name, None)
737
+ if method and callable(method):
738
+ resolver_func = method
739
+ if SF_DEBUG and app_config._interceptors_initialized:
740
+ print(f"[[Strawberry]] ASYNC Got resolver from root.__class__.{info.field_name}: {resolver_func}", log=False)
741
+
742
+ # Approach 4: Try to get resolver directly from the field definition
743
+ if not resolver_func and hasattr(info, '_field'):
744
+ field = info._field
745
+ # Try multiple attributes where Strawberry might store the resolver
746
+ for attr_name in ['base_resolver', 'resolver', 'wrapped_func', 'python_name']:
747
+ if hasattr(field, attr_name):
748
+ candidate = getattr(field, attr_name)
749
+ if callable(candidate):
750
+ resolver_func = candidate
751
+ break
752
+
753
+ # Fallback: just use field_name
754
+ if not resolver_func and hasattr(info, 'field_name'):
755
+ func_name = info.field_name
756
+ if SF_DEBUG and app_config._interceptors_initialized:
757
+ print(f"[[Strawberry]] ASYNC Using field_name only: {func_name}", log=False)
758
+
759
+ # If we found a resolver function, extract its details
760
+ if resolver_func:
761
+ # Unwrap decorators and get the actual function
762
+ actual_func = resolver_func
763
+ # Handle bound methods
764
+ if hasattr(actual_func, '__func__'):
765
+ actual_func = actual_func.__func__
766
+ # Handle wrapped functions
767
+ while hasattr(actual_func, '__wrapped__'):
768
+ actual_func = actual_func.__wrapped__
769
+
770
+ # CRITICAL: If we got Strawberry's _async_resolver wrapper, try to extract the real user function
771
+ # Strawberry wraps user resolvers in _async_resolver (defined in schema_converter.py)
772
+ # We need to look inside the closure to find the actual user function
773
+ if hasattr(actual_func, '__code__') and actual_func.__name__ == '_async_resolver':
774
+ if 'site-packages/strawberry' in actual_func.__code__.co_filename:
775
+ # This is Strawberry's wrapper - try to extract the real resolver from closure
776
+ if hasattr(actual_func, '__closure__') and actual_func.__closure__:
777
+ for cell in actual_func.__closure__:
778
+ try:
779
+ cell_content = cell.cell_contents
780
+ # Look for a callable that's not from site-packages
781
+ if callable(cell_content) and hasattr(cell_content, '__code__'):
782
+ cell_filename = cell_content.__code__.co_filename
783
+ if 'site-packages' not in cell_filename and 'dist-packages' not in cell_filename:
784
+ # Found the user function!
785
+ actual_func = cell_content
786
+ if SF_DEBUG and app_config._interceptors_initialized:
787
+ print(f"[[Strawberry]] ASYNC Unwrapped _async_resolver to user function: {cell_content.__name__} @ {cell_filename}", log=False)
788
+ break
789
+ except (AttributeError, ValueError):
790
+ pass
791
+
792
+ if hasattr(actual_func, '__code__'):
793
+ filename = actual_func.__code__.co_filename
794
+ func_name = actual_func.__name__
795
+ line_no = actual_func.__code__.co_firstlineno
796
+ if SF_DEBUG and app_config._interceptors_initialized:
797
+ print(f"[[Strawberry]] ASYNC Resolver details: {func_name} @ {filename}:{line_no}, is_async={inspect.iscoroutinefunction(actual_func)}", log=False)
798
+
799
+ # CRITICAL: Skip profiling telemetry collection resolvers to prevent infinite loops
800
+ _TELEMETRY_RESOLVERS_TO_SKIP = {
801
+ 'collectFunctionSpans', 'collect_function_spans',
802
+ 'collectNetworkRequest', 'collect_network_request',
803
+ 'collectNetworkHops', 'collect_network_hops',
804
+ 'collectLogs', 'collect_logs',
805
+ 'collectPrintStatements', 'collect_print_statements',
806
+ 'collectExceptions', 'collect_exceptions',
807
+ 'collectConsoleLogs', 'collect_console_logs',
808
+ }
809
+
810
+ # Also check the GraphQL field name from info (might differ from Python function name)
811
+ graphql_field_name = info.field_name if hasattr(info, 'field_name') else None
812
+
813
+ # Detect if this is a trivial field resolver (simple property accessor)
814
+ # IMPORTANT: Only skip trivial resolvers if we couldn't extract func details
815
+ # If we successfully extracted filename/line_no, it's a real resolver we should capture
816
+ is_trivial_resolver = False
817
+ if not resolver_func:
818
+ # No resolver function found at all - this is a property accessor
819
+ is_trivial_resolver = True
820
+ elif filename == "<unknown>" or func_name == "<unknown>":
821
+ # Failed to extract details - likely trivial
822
+ is_trivial_resolver = True
823
+ elif resolver_func and hasattr(resolver_func, '__code__'):
824
+ code = resolver_func.__code__
825
+ # Very short bytecode (< 30 bytes) indicates simple property access like "return self.field"
826
+ if code.co_code and len(code.co_code) < 30:
827
+ is_trivial_resolver = True
828
+
829
+ # Check both Python function name and GraphQL field name for telemetry resolvers
830
+ is_telemetry_resolver = (
831
+ func_name in _TELEMETRY_RESOLVERS_TO_SKIP or
832
+ graphql_field_name in _TELEMETRY_RESOLVERS_TO_SKIP
833
+ )
834
+
835
+ if is_telemetry_resolver or is_trivial_resolver:
836
+ # Skip profiling entirely - no network hops, no function spans
837
+ # This prevents infinite loops when function spans are sent to GraphQL endpoint
838
+ return await _next(root, info, *args, **kwargs)
839
+
840
+ # CRITICAL: Skip resolvers from site-packages (unless explicitly enabled)
841
+ # This respects the SF_FUNCSPAN_CAPTURE_INSTALLED_PACKAGES setting
842
+ if not SF_FUNCSPAN_CAPTURE_INSTALLED_PACKAGES and filename != "<unknown>":
843
+ if "site-packages" in filename or "dist-packages" in filename:
844
+ return await _next(root, info, *args, **kwargs)
845
+
846
+ # Auto-profile async GraphQL resolvers with function span capture
847
+ # This works around Python's sys.setprofile limitation with async functions
848
+ try:
849
+ if _HAS_NATIVE and app_config._interceptors_initialized:
850
+ # Apply sampling BEFORE pushing span - respect per-function sample_rate config
851
+ try:
852
+ func_config = _sffuncspan.get_function_config(filename, func_name)
853
+ sample_rate = func_config.get('sample_rate', 1.0) if func_config else 1.0
854
+ if sample_rate < 1.0 and random.random() >= sample_rate:
855
+ # Sampled out - skip span capture, but still do network hop tracing
856
+ tracer = self._make_tracer(info)
857
+ sys.setprofile(tracer)
858
+ try:
859
+ result = await _next(root, info, *args, **kwargs)
860
+ finally:
861
+ sys.setprofile(None)
862
+ return result
863
+ except:
864
+ # If config lookup fails, proceed with capture (default to capturing)
865
+ pass
866
+
867
+ # Generate span ID
868
+ span_id = _sffuncspan.generate_span_id()
869
+ parent_span_id = _sffuncspan.peek_parent_span_id()
870
+ _sffuncspan.push_span(span_id)
871
+
872
+ if SF_DEBUG and app_config._interceptors_initialized:
873
+ print(f"[[Strawberry]] ASYNC: Pushed span_id={span_id} for resolver={func_name}, parent={parent_span_id}", log=False)
874
+ # Verify it's in the stack
875
+ current = get_current_function_span_id()
876
+ print(f"[[Strawberry]] ASYNC: get_current_function_span_id()={current}", log=False)
877
+
878
+ # resolver_func, func_name, filename, line_no already set above from info
879
+
880
+ # Capture arguments (lightweight - just resolver name)
881
+ arguments_json = json.dumps({
882
+ "resolver": func_name,
883
+ "root_type": type(root).__name__ if root else None
884
+ })
885
+
886
+ # Record start time
887
+ start_ns = _sffuncspan.get_epoch_ns()
888
+
889
+ # Note: Profiler is already installed by unified_interceptor.py
890
+
891
+ # Execute resolver - C profiler will capture all child functions
892
+ exception_occurred = True # Initialize before try block
893
+ try:
894
+ result = await _next(root, info, *args, **kwargs)
895
+ exception_occurred = False
896
+ except Exception as e:
897
+ exception_occurred = True
898
+ result = None
899
+ raise
900
+ finally:
901
+ # Record end time
902
+ end_ns = _sffuncspan.get_epoch_ns()
903
+ duration_ns = end_ns - start_ns
904
+
905
+ # Pop span
906
+ _sffuncspan.pop_span()
907
+
908
+ # Capture return value using C serializer
909
+ return_value_json = None
910
+ if not exception_occurred:
911
+ try:
912
+ # Use C serializer for full value capture (not just type metadata)
913
+ return_value_json = _sffuncspan.serialize_value(result)
914
+ except:
915
+ return_value_json = None
916
+
917
+ # Record span
918
+ try:
919
+ _, session_id = get_or_set_sf_trace_id()
920
+ _sffuncspan.record_span(
921
+ session_id,
922
+ span_id,
923
+ parent_span_id,
924
+ filename,
925
+ line_no,
926
+ 0, # column
927
+ func_name,
928
+ arguments_json,
929
+ return_value_json,
930
+ start_ns,
931
+ duration_ns,
932
+ )
933
+ if SF_DEBUG and app_config._interceptors_initialized:
934
+ print(f"[[Strawberry]] Recorded funcspan for async resolver: {func_name}", log=False)
935
+ except Exception as e:
936
+ if SF_DEBUG and app_config._interceptors_initialized:
937
+ print(f"[[Strawberry]] Failed to record funcspan: {e}", log=False)
938
+
939
+ return result
940
+ else:
941
+ # Fallback: just do network hop tracing
942
+ tracer = self._make_tracer(info)
943
+ sys.setprofile(tracer)
944
+ try:
945
+ return await _next(root, info, *args, **kwargs)
946
+ finally:
947
+ sys.setprofile(None)
948
+ except ImportError:
949
+ # sf_funcspan not available, just do network hop tracing
950
+ tracer = self._make_tracer(info)
951
+ sys.setprofile(tracer)
952
+ try:
953
+ return await _next(root, info, *args, **kwargs)
954
+ finally:
955
+ sys.setprofile(None)
956
+
957
+ # ---------------- OTEL-STYLE: Emit after request completes ---------------- #
958
+ def on_request_end(self):
959
+ """Capture response data and emit network hops AFTER GraphQL response is built."""
960
+ # Capture response data first
961
+ if SF_NETWORKHOP_CAPTURE_RESPONSE_BODY and self.execution_context.result:
962
+ try:
963
+ # GraphQL result includes data and errors
964
+ result_data = {
965
+ "data": (
966
+ self.execution_context.result.data
967
+ if self.execution_context.result.data
968
+ else None
969
+ ),
970
+ "errors": (
971
+ [str(e) for e in self.execution_context.result.errors]
972
+ if self.execution_context.result.errors
973
+ else None
974
+ ),
975
+ }
976
+ if HAS_ORJSON:
977
+ result_str = orjson.dumps(result_data, default=str)[
978
+ :_RESPONSE_LIMIT_BYTES
979
+ ]
980
+ else:
981
+ result_str = json.dumps(result_data, default=str)[
982
+ :_RESPONSE_LIMIT_BYTES
983
+ ]
984
+ self._response_data["body"] = result_str.encode("utf-8")
985
+ if SF_DEBUG and app_config._interceptors_initialized:
986
+ print(
987
+ f"[[Strawberry]] Captured GraphQL result: {len(result_str)} chars",
988
+ log=False,
989
+ )
990
+ except Exception as e:
991
+ if SF_DEBUG and app_config._interceptors_initialized:
992
+ print(
993
+ f"[[Strawberry]] Failed to capture response data: {e}",
994
+ log=False,
995
+ )
996
+
997
+ # Get captured data
998
+ req_headers = self._request_data.get("headers")
999
+ req_body = self._request_data.get("body")
1000
+ resp_headers = self._response_data.get(
1001
+ "headers"
1002
+ ) # Not typically available in GraphQL
1003
+ resp_body = self._response_data.get("body")
1004
+
1005
+ # Emit network hops for all captured resolvers
1006
+ for endpoint_info in self._captured_endpoints:
1007
+ endpoint_id = endpoint_info.get("endpoint_id")
1008
+
1009
+ try:
1010
+ _, session_id = get_or_set_sf_trace_id()
1011
+
1012
+ if SF_DEBUG and app_config._interceptors_initialized:
1013
+ print(
1014
+ f"[[Strawberry]] Emitting hop for {endpoint_info['name']}: "
1015
+ f"req_headers={'present' if req_headers else 'None'}, "
1016
+ f"req_body={len(req_body) if req_body else 0} bytes, "
1017
+ f"resp_body={len(resp_body) if resp_body else 0} bytes",
1018
+ log=False,
1019
+ )
1020
+
1021
+ # Extract raw path and query string for C to parse (if available from context)
1022
+ raw_path = None
1023
+ raw_query = b""
1024
+ try:
1025
+ if hasattr(self.execution_context, "context"):
1026
+ ctx = self.execution_context.context
1027
+ if hasattr(ctx, "request"):
1028
+ req = ctx.request
1029
+ # Try to get path - different frameworks have different attributes
1030
+ if hasattr(req, "path"):
1031
+ raw_path = str(req.path)
1032
+ elif hasattr(req, "url") and hasattr(req.url, "path"):
1033
+ raw_path = str(req.url.path)
1034
+
1035
+ # Try to get query string
1036
+ if hasattr(req, "query_string"):
1037
+ raw_query = (
1038
+ req.query_string
1039
+ if isinstance(req.query_string, bytes)
1040
+ else req.query_string.encode("utf-8")
1041
+ )
1042
+ elif (
1043
+ hasattr(req, "META") and "QUERY_STRING" in req.META
1044
+ ):
1045
+ raw_query = req.META["QUERY_STRING"].encode("utf-8")
1046
+ except Exception as e:
1047
+ if SF_DEBUG and app_config._interceptors_initialized:
1048
+ print(
1049
+ f"[[Strawberry]] Failed to extract path/query: {e}",
1050
+ log=False,
1051
+ )
1052
+
1053
+ # Use fast path if C extension available
1054
+ if endpoint_id is not None and endpoint_id >= 0:
1055
+ fast_send_network_hop_fast(
1056
+ session_id=session_id,
1057
+ endpoint_id=endpoint_id,
1058
+ raw_path=raw_path,
1059
+ raw_query_string=raw_query,
1060
+ request_headers=req_headers,
1061
+ request_body=req_body,
1062
+ response_headers=resp_headers,
1063
+ response_body=resp_body,
1064
+ )
1065
+ if SF_DEBUG and app_config._interceptors_initialized:
1066
+ print(
1067
+ f"[[Strawberry]] Emitted network hop (fast path): {endpoint_info['name']} "
1068
+ f"endpoint_id={endpoint_id} session={session_id}",
1069
+ log=False,
1070
+ )
1071
+ else:
1072
+ # Fallback to old Python API (doesn't support body/header capture)
1073
+ fast_send_network_hop(
1074
+ session_id=session_id,
1075
+ line=str(endpoint_info["line"]),
1076
+ column="0",
1077
+ name=endpoint_info["name"],
1078
+ entrypoint=endpoint_info["filename"],
1079
+ )
1080
+ if SF_DEBUG and app_config._interceptors_initialized:
1081
+ print(
1082
+ f"[[Strawberry]] Emitted network hop (fallback): {endpoint_info['name']} "
1083
+ f"session={session_id}",
1084
+ log=False,
1085
+ )
1086
+ except Exception as e: # noqa: BLE001 S110
1087
+ if SF_DEBUG and app_config._interceptors_initialized:
1088
+ print(
1089
+ f"[[Strawberry]] Failed to emit network hop: {e}", log=False
1090
+ )
1091
+
1092
+ return NetworkHopExtension
1093
+
1094
+
1095
+ def patch_strawberry_module(strawberry):
1096
+ """Patch Strawberry to ensure exceptions go through the custom excepthook."""
1097
+ global _is_strawberry_patched
1098
+ if _is_strawberry_patched:
1099
+ if SF_DEBUG and app_config._interceptors_initialized:
1100
+ print(
1101
+ "[[DEBUG]] Strawberry has already been patched, skipping. [[/DEBUG]]",
1102
+ log=False,
1103
+ )
1104
+ return
1105
+
1106
+ try:
1107
+ # Backup the original execute method from Strawberry
1108
+ original_execute = strawberry.execution.execute.execute
1109
+
1110
+ async def custom_execute(*args, **kwargs):
1111
+ try:
1112
+ if SF_DEBUG and app_config._interceptors_initialized:
1113
+ print(
1114
+ "[[DEBUG]] Executing patched Strawberry execute function. [[/DEBUG]]",
1115
+ log=False,
1116
+ )
1117
+ return await original_execute(*args, **kwargs)
1118
+ except Exception as e:
1119
+ if SF_DEBUG and app_config._interceptors_initialized:
1120
+ print(
1121
+ "[[DEBUG]] Intercepted exception in Strawberry execute. [[/DEBUG]]",
1122
+ log=False,
1123
+ )
1124
+ # Invoke custom excepthook globally
1125
+ sys.excepthook(type(e), e, e.__traceback__)
1126
+ raise
1127
+
1128
+ # Replace Strawberry's execute function with the patched version
1129
+ strawberry.execution.execute.execute = custom_execute
1130
+ _is_strawberry_patched = True
1131
+ if SF_DEBUG and app_config._interceptors_initialized:
1132
+ print(
1133
+ "[[DEBUG]] Successfully patched Strawberry execute function. [[/DEBUG]]",
1134
+ log=False,
1135
+ )
1136
+ except Exception as error:
1137
+ if SF_DEBUG and app_config._interceptors_initialized:
1138
+ print(
1139
+ f"[[DEBUG]] Failed to patch Strawberry: {error}. [[/DEBUG]]", log=False
1140
+ )
1141
+
1142
+
1143
+ class CustomImportHook:
1144
+ """Import hook to intercept the import of 'strawberry' modules."""
1145
+
1146
+ def find_spec(self, fullname, path, target=None):
1147
+ global _is_strawberry_patched
1148
+ if fullname == "strawberry" and not _is_strawberry_patched:
1149
+ if SF_DEBUG and app_config._interceptors_initialized:
1150
+ print(
1151
+ f"[[DEBUG]] Intercepting import of {fullname}. [[/DEBUG]]",
1152
+ log=False,
1153
+ )
1154
+ return find_spec(fullname)
1155
+ if fullname.startswith("strawberry_django"):
1156
+ return None # Let default import handle strawberry_django
1157
+
1158
+ def exec_module(self, module):
1159
+ if SF_DEBUG and app_config._interceptors_initialized:
1160
+ print(
1161
+ f"[[DEBUG]] Executing module: {module.__name__}. [[/DEBUG]]", log=False
1162
+ )
1163
+ # Execute the module normally
1164
+ module_spec = module.__spec__
1165
+ if module_spec and module_spec.loader:
1166
+ module_spec.loader.exec_module(module)
1167
+ # Once strawberry is loaded, patch it
1168
+ if module.__name__ == "strawberry" and not _is_strawberry_patched:
1169
+ patch_strawberry_module(module)
1170
+
1171
+
1172
+ def patch_schema():
1173
+ """Patch strawberry.Schema to include both Sailfish and NetworkHop extensions by default."""
1174
+ try:
1175
+ import strawberry
1176
+
1177
+ original_schema_init = strawberry.Schema.__init__
1178
+
1179
+ def patched_schema_init(self, *args, extensions=None, **kwargs):
1180
+ if extensions is None:
1181
+ extensions = []
1182
+
1183
+ # Add the custom error handling extension
1184
+ sailfish_ext = get_extension()
1185
+ if sailfish_ext not in extensions:
1186
+ extensions.append(sailfish_ext)
1187
+
1188
+ # Add the network hop extension
1189
+ hop_ext = get_network_hop_extension()
1190
+ if hop_ext not in extensions:
1191
+ extensions.append(hop_ext)
1192
+
1193
+ # Call the original constructor
1194
+ original_schema_init(self, *args, extensions=extensions, **kwargs)
1195
+
1196
+ if SF_DEBUG and app_config._interceptors_initialized:
1197
+ print(
1198
+ "[[DEBUG]] Patched strawberry.Schema to include Sailfish & NetworkHop extensions. [[/DEBUG]]",
1199
+ log=False,
1200
+ )
1201
+
1202
+ # Apply the patch
1203
+ strawberry.Schema.__init__ = patched_schema_init
1204
+
1205
+ if SF_DEBUG and app_config._interceptors_initialized:
1206
+ print(
1207
+ "[[DEBUG]] Successfully patched strawberry.Schema. [[/DEBUG]]",
1208
+ log=False,
1209
+ )
1210
+ except ImportError:
1211
+ if SF_DEBUG and app_config._interceptors_initialized:
1212
+ print(
1213
+ "[[DEBUG]] Strawberry is not installed. Skipping schema patching. [[/DEBUG]]",
1214
+ log=False,
1215
+ )
1216
+
1217
+
1218
+ def patch_views():
1219
+ """
1220
+ Patch Strawberry view classes to capture and print request data on errors.
1221
+ This helps debug malformed requests when STRAWBERRY_DEBUG is enabled.
1222
+ Also transmits exceptions with full stack traces when CAPTURE_STRAWBERRY_ERRORS_WITH_DATA is enabled.
1223
+ """
1224
+ if not STRAWBERRY_DEBUG and not CAPTURE_STRAWBERRY_ERRORS_WITH_DATA:
1225
+ return # Skip patching if neither debug mode nor capture mode is enabled
1226
+
1227
+ try:
1228
+ # Try to import Strawberry Django view
1229
+ try:
1230
+ from strawberry.django.views import GraphQLView as DjangoGraphQLView
1231
+
1232
+ _patch_view_class(DjangoGraphQLView, "Django")
1233
+ except ImportError:
1234
+ if SF_DEBUG and app_config._interceptors_initialized:
1235
+ print(
1236
+ "[[DEBUG]] Strawberry Django view not found. [[/DEBUG]]", log=False
1237
+ )
1238
+
1239
+ # Try to import base async view (used by other integrations)
1240
+ try:
1241
+ from strawberry.http.async_base_view import AsyncBaseHTTPView
1242
+
1243
+ _patch_async_base_view(AsyncBaseHTTPView)
1244
+ except ImportError:
1245
+ if SF_DEBUG and app_config._interceptors_initialized:
1246
+ print(
1247
+ "[[DEBUG]] Strawberry AsyncBaseHTTPView not found. [[/DEBUG]]",
1248
+ log=False,
1249
+ )
1250
+
1251
+ except Exception as e:
1252
+ if SF_DEBUG and app_config._interceptors_initialized:
1253
+ print(
1254
+ f"[[DEBUG]] Failed to patch Strawberry views: {e}. [[/DEBUG]]",
1255
+ log=False,
1256
+ )
1257
+
1258
+
1259
+ def _patch_view_class(view_class, integration_name):
1260
+ """Patch a Strawberry view class to capture request data on errors."""
1261
+ if hasattr(view_class, "_sf_patched"):
1262
+ return # Already patched
1263
+
1264
+ original_dispatch = view_class.dispatch
1265
+
1266
+ async def patched_dispatch(self, request, *args, **kwargs):
1267
+ # Capture raw request body before processing
1268
+ raw_body = None
1269
+ if STRAWBERRY_DEBUG or CAPTURE_STRAWBERRY_ERRORS_WITH_DATA:
1270
+ try:
1271
+ raw_body = request.body if hasattr(request, "body") else None
1272
+ except Exception:
1273
+ pass
1274
+
1275
+ try:
1276
+ return await original_dispatch(self, request, *args, **kwargs)
1277
+ except Exception as e:
1278
+ if (
1279
+ STRAWBERRY_DEBUG or CAPTURE_STRAWBERRY_ERRORS_WITH_DATA
1280
+ ) and raw_body is not None:
1281
+ _print_request_debug_info(raw_body, e, integration_name)
1282
+ raise
1283
+
1284
+ view_class.dispatch = patched_dispatch
1285
+ view_class._sf_patched = True
1286
+
1287
+ if SF_DEBUG and app_config._interceptors_initialized:
1288
+ print(
1289
+ f"[[DEBUG]] Patched Strawberry {integration_name} view for error debugging. [[/DEBUG]]",
1290
+ log=False,
1291
+ )
1292
+
1293
+
1294
+ def _patch_async_base_view(view_class):
1295
+ """Patch AsyncBaseHTTPView to capture request data on parse errors."""
1296
+ if hasattr(view_class, "_sf_parse_patched"):
1297
+ return # Already patched
1298
+
1299
+ original_parse = view_class.parse_http_body
1300
+
1301
+ async def patched_parse_http_body(self, request_adapter):
1302
+ # Capture raw body before parsing (but avoid consuming the stream twice)
1303
+ raw_body = None
1304
+ if STRAWBERRY_DEBUG or CAPTURE_STRAWBERRY_ERRORS_WITH_DATA:
1305
+ try:
1306
+ # Read the body once
1307
+ raw_body = await request_adapter.get_body()
1308
+
1309
+ # Patch request_adapter.get_body to return cached body
1310
+ # (body streams can only be read once)
1311
+ async def cached_get_body():
1312
+ return raw_body
1313
+
1314
+ request_adapter.get_body = cached_get_body
1315
+ except Exception:
1316
+ pass
1317
+
1318
+ try:
1319
+ return await original_parse(self, request_adapter)
1320
+ except Exception as e:
1321
+ logger.info("=" * 20 + " <STRAWBERRY> " + "=" * 20)
1322
+ logger.error(e)
1323
+ logger.info("=" * 20 + " </STRAWBERRY> " + "=" * 20)
1324
+ if (
1325
+ STRAWBERRY_DEBUG or CAPTURE_STRAWBERRY_ERRORS_WITH_DATA
1326
+ ) and raw_body is not None:
1327
+ _print_request_debug_info(raw_body, e, "AsyncBaseHTTPView")
1328
+ raise
1329
+
1330
+ view_class.parse_http_body = patched_parse_http_body
1331
+ view_class._sf_parse_patched = True
1332
+
1333
+ if SF_DEBUG and app_config._interceptors_initialized:
1334
+ print(
1335
+ "[[DEBUG]] Patched Strawberry AsyncBaseHTTPView.parse_http_body for error debugging. [[/DEBUG]]",
1336
+ log=False,
1337
+ )
1338
+
1339
+
1340
+ def _count_traceback_frames(tb):
1341
+ """Count the number of frames in a traceback."""
1342
+ count = 0
1343
+ while tb is not None:
1344
+ count += 1
1345
+ tb = tb.tb_next
1346
+ return count
1347
+
1348
+
1349
+ def _print_request_debug_info(raw_body, exception, source):
1350
+ """Print debug information about the request that caused an error."""
1351
+
1352
+ # Transmit exception to Sailfish with full stack trace if enabled
1353
+ if CAPTURE_STRAWBERRY_ERRORS_WITH_DATA:
1354
+ try:
1355
+ # Verify that the exception has a traceback attached
1356
+ if (
1357
+ not hasattr(exception, "__traceback__")
1358
+ or exception.__traceback__ is None
1359
+ ):
1360
+ if SF_DEBUG and app_config._interceptors_initialized:
1361
+ print(
1362
+ f"[[STRAWBERRY_DEBUG]] WARNING: Exception {type(exception).__name__} has no __traceback__ attribute!",
1363
+ log=False,
1364
+ )
1365
+ else:
1366
+ if SF_DEBUG and app_config._interceptors_initialized:
1367
+ print(
1368
+ f"[[STRAWBERRY_DEBUG]] Exception has traceback with {_count_traceback_frames(exception.__traceback__)} frames",
1369
+ log=False,
1370
+ )
1371
+
1372
+ transmit_exception_to_sailfish(exception, force_transmit=False)
1373
+ if SF_DEBUG and app_config._interceptors_initialized:
1374
+ print(
1375
+ f"[[STRAWBERRY_DEBUG]] Transmitted exception to Sailfish: {type(exception).__name__}",
1376
+ log=False,
1377
+ )
1378
+ except Exception as transmit_err:
1379
+ if SF_DEBUG and app_config._interceptors_initialized:
1380
+ print(
1381
+ f"[[STRAWBERRY_DEBUG]] Failed to transmit exception: {transmit_err}",
1382
+ log=False,
1383
+ )
1384
+
1385
+ print(
1386
+ f"[[STRAWBERRY_DEBUG]] Transmission error traceback:\n{traceback.format_exc()}",
1387
+ log=False,
1388
+ )
1389
+
1390
+ # Print debug info if STRAWBERRY_DEBUG is enabled
1391
+ if not STRAWBERRY_DEBUG:
1392
+ return # Skip printing if debug mode is disabled
1393
+
1394
+ print("\n" + "=" * 80, log=False)
1395
+ print(f"[[STRAWBERRY_DEBUG]] Error in {source}", log=False)
1396
+ print("=" * 80, log=False)
1397
+
1398
+ # Print the exception
1399
+ print(f"\nException: {type(exception).__name__}: {exception}", log=False)
1400
+ print("\nTraceback:", log=False)
1401
+ print(traceback.format_exc(), log=False)
1402
+
1403
+ # Print raw body
1404
+ print("\n" + "-" * 80, log=False)
1405
+ print("Raw HTTP Body (bytes):", log=False)
1406
+ print("-" * 80, log=False)
1407
+ if isinstance(raw_body, bytes):
1408
+ print(f"Length: {len(raw_body)} bytes", log=False)
1409
+ print(f"Raw: {raw_body!r}", log=False)
1410
+
1411
+ # Try to decode and pretty-print as JSON
1412
+ try:
1413
+ decoded = raw_body.decode("utf-8")
1414
+ print(f"\nDecoded (UTF-8): {decoded}", log=False)
1415
+
1416
+ # Try to parse as JSON
1417
+ try:
1418
+ if HAS_ORJSON:
1419
+ parsed = orjson.loads(decoded)
1420
+ else:
1421
+ parsed = json.loads(decoded)
1422
+ print(f"\nParsed JSON (type: {type(parsed).__name__}):", log=False)
1423
+ if HAS_ORJSON:
1424
+ parsed = print(orjson.dumps(parsed, indent=2), log=False)
1425
+ else:
1426
+ parsed = print(json.dumps(parsed, indent=2), log=False)
1427
+ except json.JSONDecodeError as json_err:
1428
+ print(f"\nFailed to parse as JSON: {json_err}", log=False)
1429
+ except UnicodeDecodeError as decode_err:
1430
+ print(f"\nFailed to decode as UTF-8: {decode_err}", log=False)
1431
+ else:
1432
+ print(f"Body type: {type(raw_body).__name__}", log=False)
1433
+ print(f"Body: {raw_body!r}", log=False)
1434
+
1435
+ print("\n" + "=" * 80, log=False)
1436
+ print("[[/STRAWBERRY_DEBUG]]", log=False)
1437
+ print("=" * 80 + "\n", log=False)
1438
+
1439
+
1440
+ def patch_strawberry():
1441
+ """
1442
+ Main entry point for patching Strawberry GraphQL.
1443
+ Applies both schema extensions and error debugging patches.
1444
+ """
1445
+ patch_schema()
1446
+ patch_views()