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.

Files changed (86) hide show
  1. sf_veritas/.gitignore +2 -0
  2. sf_veritas/__init__.py +4 -0
  3. sf_veritas/app_config.py +49 -0
  4. sf_veritas/cli.py +336 -0
  5. sf_veritas/constants.py +3 -0
  6. sf_veritas/custom_excepthook.py +285 -0
  7. sf_veritas/custom_log_handler.py +53 -0
  8. sf_veritas/custom_output_wrapper.py +107 -0
  9. sf_veritas/custom_print.py +34 -0
  10. sf_veritas/django_app.py +5 -0
  11. sf_veritas/env_vars.py +83 -0
  12. sf_veritas/exception_handling_middleware.py +18 -0
  13. sf_veritas/exception_metaclass.py +69 -0
  14. sf_veritas/frame_tools.py +112 -0
  15. sf_veritas/import_hook.py +62 -0
  16. sf_veritas/infra_details/__init__.py +3 -0
  17. sf_veritas/infra_details/get_infra_details.py +24 -0
  18. sf_veritas/infra_details/kubernetes/__init__.py +3 -0
  19. sf_veritas/infra_details/kubernetes/get_cluster_name.py +147 -0
  20. sf_veritas/infra_details/kubernetes/get_details.py +7 -0
  21. sf_veritas/infra_details/running_on/__init__.py +17 -0
  22. sf_veritas/infra_details/running_on/kubernetes.py +11 -0
  23. sf_veritas/interceptors.py +252 -0
  24. sf_veritas/local_env_detect.py +118 -0
  25. sf_veritas/package_metadata.py +6 -0
  26. sf_veritas/patches/__init__.py +0 -0
  27. sf_veritas/patches/concurrent_futures.py +19 -0
  28. sf_veritas/patches/constants.py +1 -0
  29. sf_veritas/patches/exceptions.py +82 -0
  30. sf_veritas/patches/multiprocessing.py +32 -0
  31. sf_veritas/patches/network_libraries/__init__.py +51 -0
  32. sf_veritas/patches/network_libraries/aiohttp.py +100 -0
  33. sf_veritas/patches/network_libraries/curl_cffi.py +93 -0
  34. sf_veritas/patches/network_libraries/http_client.py +64 -0
  35. sf_veritas/patches/network_libraries/httpcore.py +152 -0
  36. sf_veritas/patches/network_libraries/httplib2.py +76 -0
  37. sf_veritas/patches/network_libraries/httpx.py +123 -0
  38. sf_veritas/patches/network_libraries/niquests.py +192 -0
  39. sf_veritas/patches/network_libraries/pycurl.py +71 -0
  40. sf_veritas/patches/network_libraries/requests.py +187 -0
  41. sf_veritas/patches/network_libraries/tornado.py +139 -0
  42. sf_veritas/patches/network_libraries/treq.py +122 -0
  43. sf_veritas/patches/network_libraries/urllib_request.py +129 -0
  44. sf_veritas/patches/network_libraries/utils.py +101 -0
  45. sf_veritas/patches/os.py +17 -0
  46. sf_veritas/patches/threading.py +32 -0
  47. sf_veritas/patches/web_frameworks/__init__.py +45 -0
  48. sf_veritas/patches/web_frameworks/aiohttp.py +133 -0
  49. sf_veritas/patches/web_frameworks/async_websocket_consumer.py +132 -0
  50. sf_veritas/patches/web_frameworks/blacksheep.py +107 -0
  51. sf_veritas/patches/web_frameworks/bottle.py +142 -0
  52. sf_veritas/patches/web_frameworks/cherrypy.py +246 -0
  53. sf_veritas/patches/web_frameworks/django.py +307 -0
  54. sf_veritas/patches/web_frameworks/eve.py +138 -0
  55. sf_veritas/patches/web_frameworks/falcon.py +229 -0
  56. sf_veritas/patches/web_frameworks/fastapi.py +145 -0
  57. sf_veritas/patches/web_frameworks/flask.py +186 -0
  58. sf_veritas/patches/web_frameworks/klein.py +40 -0
  59. sf_veritas/patches/web_frameworks/litestar.py +217 -0
  60. sf_veritas/patches/web_frameworks/pyramid.py +89 -0
  61. sf_veritas/patches/web_frameworks/quart.py +155 -0
  62. sf_veritas/patches/web_frameworks/robyn.py +114 -0
  63. sf_veritas/patches/web_frameworks/sanic.py +120 -0
  64. sf_veritas/patches/web_frameworks/starlette.py +144 -0
  65. sf_veritas/patches/web_frameworks/strawberry.py +269 -0
  66. sf_veritas/patches/web_frameworks/tornado.py +129 -0
  67. sf_veritas/patches/web_frameworks/utils.py +55 -0
  68. sf_veritas/print_override.py +13 -0
  69. sf_veritas/regular_data_transmitter.py +358 -0
  70. sf_veritas/request_interceptor.py +399 -0
  71. sf_veritas/request_utils.py +104 -0
  72. sf_veritas/server_status.py +1 -0
  73. sf_veritas/shutdown_flag.py +11 -0
  74. sf_veritas/subprocess_startup.py +3 -0
  75. sf_veritas/test_cli.py +145 -0
  76. sf_veritas/thread_local.py +436 -0
  77. sf_veritas/timeutil.py +114 -0
  78. sf_veritas/transmit_exception_to_sailfish.py +28 -0
  79. sf_veritas/transmitter.py +58 -0
  80. sf_veritas/types.py +44 -0
  81. sf_veritas/unified_interceptor.py +323 -0
  82. sf_veritas/utils.py +39 -0
  83. sf_veritas-0.9.7.dist-info/METADATA +83 -0
  84. sf_veritas-0.9.7.dist-info/RECORD +86 -0
  85. sf_veritas-0.9.7.dist-info/WHEEL +4 -0
  86. 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