sf-veritas 0.10.3__cp311-cp311-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.

Potentially problematic release.


This version of sf-veritas might be problematic. Click here for more details.

Files changed (132) hide show
  1. sf_veritas/__init__.py +20 -0
  2. sf_veritas/_sffastlog.c +889 -0
  3. sf_veritas/_sffastlog.cpython-311-x86_64-linux-gnu.so +0 -0
  4. sf_veritas/_sffastnet.c +924 -0
  5. sf_veritas/_sffastnet.cpython-311-x86_64-linux-gnu.so +0 -0
  6. sf_veritas/_sffastnetworkrequest.c +730 -0
  7. sf_veritas/_sffastnetworkrequest.cpython-311-x86_64-linux-gnu.so +0 -0
  8. sf_veritas/_sffuncspan.c +2155 -0
  9. sf_veritas/_sffuncspan.cpython-311-x86_64-linux-gnu.so +0 -0
  10. sf_veritas/_sffuncspan_config.c +617 -0
  11. sf_veritas/_sffuncspan_config.cpython-311-x86_64-linux-gnu.so +0 -0
  12. sf_veritas/_sfheadercheck.c +341 -0
  13. sf_veritas/_sfheadercheck.cpython-311-x86_64-linux-gnu.so +0 -0
  14. sf_veritas/_sfnetworkhop.c +1451 -0
  15. sf_veritas/_sfnetworkhop.cpython-311-x86_64-linux-gnu.so +0 -0
  16. sf_veritas/_sfservice.c +1175 -0
  17. sf_veritas/_sfservice.cpython-311-x86_64-linux-gnu.so +0 -0
  18. sf_veritas/_sfteepreload.c +5167 -0
  19. sf_veritas/app_config.py +49 -0
  20. sf_veritas/cli.py +336 -0
  21. sf_veritas/constants.py +10 -0
  22. sf_veritas/custom_excepthook.py +304 -0
  23. sf_veritas/custom_log_handler.py +129 -0
  24. sf_veritas/custom_output_wrapper.py +144 -0
  25. sf_veritas/custom_print.py +146 -0
  26. sf_veritas/django_app.py +5 -0
  27. sf_veritas/env_vars.py +186 -0
  28. sf_veritas/exception_handling_middleware.py +18 -0
  29. sf_veritas/exception_metaclass.py +69 -0
  30. sf_veritas/fast_frame_info.py +116 -0
  31. sf_veritas/fast_network_hop.py +293 -0
  32. sf_veritas/frame_tools.py +112 -0
  33. sf_veritas/funcspan_config_loader.py +556 -0
  34. sf_veritas/function_span_profiler.py +1174 -0
  35. sf_veritas/import_hook.py +62 -0
  36. sf_veritas/infra_details/__init__.py +3 -0
  37. sf_veritas/infra_details/get_infra_details.py +24 -0
  38. sf_veritas/infra_details/kubernetes/__init__.py +3 -0
  39. sf_veritas/infra_details/kubernetes/get_cluster_name.py +147 -0
  40. sf_veritas/infra_details/kubernetes/get_details.py +7 -0
  41. sf_veritas/infra_details/running_on/__init__.py +17 -0
  42. sf_veritas/infra_details/running_on/kubernetes.py +11 -0
  43. sf_veritas/interceptors.py +497 -0
  44. sf_veritas/libsfnettee.so +0 -0
  45. sf_veritas/local_env_detect.py +118 -0
  46. sf_veritas/package_metadata.py +6 -0
  47. sf_veritas/patches/__init__.py +0 -0
  48. sf_veritas/patches/concurrent_futures.py +19 -0
  49. sf_veritas/patches/constants.py +1 -0
  50. sf_veritas/patches/exceptions.py +82 -0
  51. sf_veritas/patches/multiprocessing.py +32 -0
  52. sf_veritas/patches/network_libraries/__init__.py +76 -0
  53. sf_veritas/patches/network_libraries/aiohttp.py +281 -0
  54. sf_veritas/patches/network_libraries/curl_cffi.py +363 -0
  55. sf_veritas/patches/network_libraries/http_client.py +419 -0
  56. sf_veritas/patches/network_libraries/httpcore.py +515 -0
  57. sf_veritas/patches/network_libraries/httplib2.py +204 -0
  58. sf_veritas/patches/network_libraries/httpx.py +515 -0
  59. sf_veritas/patches/network_libraries/niquests.py +211 -0
  60. sf_veritas/patches/network_libraries/pycurl.py +385 -0
  61. sf_veritas/patches/network_libraries/requests.py +633 -0
  62. sf_veritas/patches/network_libraries/tornado.py +341 -0
  63. sf_veritas/patches/network_libraries/treq.py +270 -0
  64. sf_veritas/patches/network_libraries/urllib_request.py +468 -0
  65. sf_veritas/patches/network_libraries/utils.py +398 -0
  66. sf_veritas/patches/os.py +17 -0
  67. sf_veritas/patches/threading.py +218 -0
  68. sf_veritas/patches/web_frameworks/__init__.py +54 -0
  69. sf_veritas/patches/web_frameworks/aiohttp.py +793 -0
  70. sf_veritas/patches/web_frameworks/async_websocket_consumer.py +317 -0
  71. sf_veritas/patches/web_frameworks/blacksheep.py +527 -0
  72. sf_veritas/patches/web_frameworks/bottle.py +502 -0
  73. sf_veritas/patches/web_frameworks/cherrypy.py +678 -0
  74. sf_veritas/patches/web_frameworks/cors_utils.py +122 -0
  75. sf_veritas/patches/web_frameworks/django.py +944 -0
  76. sf_veritas/patches/web_frameworks/eve.py +395 -0
  77. sf_veritas/patches/web_frameworks/falcon.py +926 -0
  78. sf_veritas/patches/web_frameworks/fastapi.py +724 -0
  79. sf_veritas/patches/web_frameworks/flask.py +520 -0
  80. sf_veritas/patches/web_frameworks/klein.py +501 -0
  81. sf_veritas/patches/web_frameworks/litestar.py +551 -0
  82. sf_veritas/patches/web_frameworks/pyramid.py +428 -0
  83. sf_veritas/patches/web_frameworks/quart.py +824 -0
  84. sf_veritas/patches/web_frameworks/robyn.py +697 -0
  85. sf_veritas/patches/web_frameworks/sanic.py +857 -0
  86. sf_veritas/patches/web_frameworks/starlette.py +723 -0
  87. sf_veritas/patches/web_frameworks/strawberry.py +813 -0
  88. sf_veritas/patches/web_frameworks/tornado.py +481 -0
  89. sf_veritas/patches/web_frameworks/utils.py +91 -0
  90. sf_veritas/print_override.py +13 -0
  91. sf_veritas/regular_data_transmitter.py +409 -0
  92. sf_veritas/request_interceptor.py +401 -0
  93. sf_veritas/request_utils.py +550 -0
  94. sf_veritas/server_status.py +1 -0
  95. sf_veritas/shutdown_flag.py +11 -0
  96. sf_veritas/subprocess_startup.py +3 -0
  97. sf_veritas/test_cli.py +145 -0
  98. sf_veritas/thread_local.py +970 -0
  99. sf_veritas/timeutil.py +114 -0
  100. sf_veritas/transmit_exception_to_sailfish.py +28 -0
  101. sf_veritas/transmitter.py +132 -0
  102. sf_veritas/types.py +47 -0
  103. sf_veritas/unified_interceptor.py +1580 -0
  104. sf_veritas/utils.py +39 -0
  105. sf_veritas-0.10.3.dist-info/METADATA +97 -0
  106. sf_veritas-0.10.3.dist-info/RECORD +132 -0
  107. sf_veritas-0.10.3.dist-info/WHEEL +5 -0
  108. sf_veritas-0.10.3.dist-info/entry_points.txt +2 -0
  109. sf_veritas-0.10.3.dist-info/top_level.txt +1 -0
  110. sf_veritas.libs/libbrotlicommon-6ce2a53c.so.1.0.6 +0 -0
  111. sf_veritas.libs/libbrotlidec-811d1be3.so.1.0.6 +0 -0
  112. sf_veritas.libs/libcom_err-730ca923.so.2.1 +0 -0
  113. sf_veritas.libs/libcrypt-52aca757.so.1.1.0 +0 -0
  114. sf_veritas.libs/libcrypto-bdaed0ea.so.1.1.1k +0 -0
  115. sf_veritas.libs/libcurl-eaa3cf66.so.4.5.0 +0 -0
  116. sf_veritas.libs/libgssapi_krb5-323bbd21.so.2.2 +0 -0
  117. sf_veritas.libs/libidn2-2f4a5893.so.0.3.6 +0 -0
  118. sf_veritas.libs/libk5crypto-9a74ff38.so.3.1 +0 -0
  119. sf_veritas.libs/libkeyutils-2777d33d.so.1.6 +0 -0
  120. sf_veritas.libs/libkrb5-a55300e8.so.3.3 +0 -0
  121. sf_veritas.libs/libkrb5support-e6594cfc.so.0.1 +0 -0
  122. sf_veritas.libs/liblber-2-d20824ef.4.so.2.10.9 +0 -0
  123. sf_veritas.libs/libldap-2-cea2a960.4.so.2.10.9 +0 -0
  124. sf_veritas.libs/libnghttp2-39367a22.so.14.17.0 +0 -0
  125. sf_veritas.libs/libpcre2-8-516f4c9d.so.0.7.1 +0 -0
  126. sf_veritas.libs/libpsl-99becdd3.so.5.3.1 +0 -0
  127. sf_veritas.libs/libsasl2-7de4d792.so.3.0.0 +0 -0
  128. sf_veritas.libs/libselinux-d0805dcb.so.1 +0 -0
  129. sf_veritas.libs/libssh-c11d285b.so.4.8.7 +0 -0
  130. sf_veritas.libs/libssl-60250281.so.1.1.1k +0 -0
  131. sf_veritas.libs/libunistring-05abdd40.so.2.1.0 +0 -0
  132. sf_veritas.libs/libuuid-95b83d40.so.1.3.0 +0 -0
@@ -0,0 +1,69 @@
1
+ from abc import ABCMeta
2
+
3
+ from .custom_excepthook import transmit_exception
4
+
5
+
6
+ class ExceptionMeta(ABCMeta):
7
+ """
8
+ Metaclass to add `capture_even_if_caught` functionality to exceptions
9
+ and provide a `transmit_to_sailfish` method for all exceptions.
10
+ """
11
+
12
+ def __new__(cls, name, bases, dct):
13
+ # Wrap or define the `__init__` method
14
+ if "__init__" in dct:
15
+ original_init = dct["__init__"]
16
+
17
+ def wrapped_init(self, *args, **kwargs):
18
+ # Add the `capture_even_if_caught` attribute
19
+ self.capture_even_if_caught = kwargs.pop(
20
+ "capture_even_if_caught", False
21
+ )
22
+ self._handled = False # Ensure `_handled` is initialized
23
+ original_init(self, *args, **kwargs)
24
+
25
+ dct["__init__"] = wrapped_init
26
+ else:
27
+
28
+ def default_init(self, *args, **kwargs):
29
+ # Add the `capture_even_if_caught` attribute
30
+ self.capture_even_if_caught = kwargs.pop(
31
+ "capture_even_if_caught", False
32
+ )
33
+ self._handled = False # Ensure `_handled` is initialized
34
+ super(Exception, self).__init__(*args, **kwargs)
35
+
36
+ dct["__init__"] = default_init
37
+
38
+ # Add `transmit_to_sailfish` method to all exceptions
39
+ def transmit_to_sailfish(self, was_caught: bool = False):
40
+ """
41
+ Transmit this exception to Sailfish.
42
+ """
43
+ if not getattr(self, "_handled", False):
44
+ transmit_exception(type(self), self, self.__traceback__, was_caught)
45
+ setattr(
46
+ self, "_handled", True
47
+ ) # Mark as handled to prevent duplication
48
+
49
+ dct["transmit_to_sailfish"] = transmit_to_sailfish
50
+
51
+ return super().__new__(cls, name, bases, dct)
52
+
53
+ def __call__(cls, *args, **kwargs):
54
+ """
55
+ Intercept exception instantiation to handle `capture_even_if_caught`.
56
+ """
57
+ instance = super().__call__(*args, **kwargs)
58
+ # Automatically handle `capture_even_if_caught` exceptions
59
+ if getattr(instance, "capture_even_if_caught", False):
60
+ instance.transmit_to_sailfish()
61
+ return instance
62
+
63
+
64
+ class PatchedException(Exception, metaclass=ExceptionMeta):
65
+ """
66
+ A patched version of the built-in Exception class with universal interception capabilities.
67
+ """
68
+
69
+ pass
@@ -0,0 +1,116 @@
1
+ """
2
+ Ultra-fast frame introspection using C extension.
3
+ Falls back to Python if C extension is not available.
4
+ """
5
+
6
+ import sysconfig
7
+ from functools import lru_cache
8
+ from typing import Optional, Tuple
9
+
10
+ # Try to import C extension
11
+ try:
12
+ from . import _sfframeinfo
13
+
14
+ _FAST_FRAME_OK = True
15
+ except ImportError:
16
+ _sfframeinfo = None
17
+ _FAST_FRAME_OK = False
18
+
19
+ # Fallback Python implementation
20
+ _STDLIB = sysconfig.get_paths()["stdlib"]
21
+ _SITE_TAGS = ("site-packages", "dist-packages")
22
+ _SKIP_PREFIXES = (_STDLIB, "/usr/local/lib/python", "/usr/lib/python")
23
+
24
+
25
+ @lru_cache(maxsize=512)
26
+ def _is_user_code_py(path: Optional[str]) -> bool:
27
+ """Python fallback for checking if path is user code."""
28
+ if not path or path.startswith("<"):
29
+ return False
30
+ for p in _SKIP_PREFIXES:
31
+ if path.startswith(p):
32
+ return False
33
+ return not any(tag in path for tag in _SITE_TAGS)
34
+
35
+
36
+ def get_code_info(func) -> Optional[Tuple[str, int, str]]:
37
+ """
38
+ Extract (filename, lineno, name) from a function object.
39
+ Returns None if not user code or extraction fails.
40
+
41
+ Uses C extension if available for ~10x speedup.
42
+ """
43
+ if _FAST_FRAME_OK:
44
+ try:
45
+ result = _sfframeinfo.get_code_info(func)
46
+ if result:
47
+ # Result is (filename, lineno, name, is_user)
48
+ return (result[0], result[1], result[2])
49
+ return None
50
+ except Exception:
51
+ pass # Fall back to Python
52
+
53
+ # Python fallback
54
+ try:
55
+ code = getattr(func, "__code__", None)
56
+ if not code:
57
+ return None
58
+
59
+ filename = code.co_filename
60
+ if not _is_user_code_py(filename):
61
+ return None
62
+
63
+ return (filename, code.co_firstlineno, func.__name__)
64
+ except Exception:
65
+ return None
66
+
67
+
68
+ def get_frame_info(frame) -> Optional[Tuple[str, int, str]]:
69
+ """
70
+ Extract (filename, lineno, name) from a frame object.
71
+ Returns None if not user code or extraction fails.
72
+
73
+ Uses C extension if available for ~10x speedup.
74
+ """
75
+ if _FAST_FRAME_OK:
76
+ try:
77
+ result = _sfframeinfo.get_frame_info(frame)
78
+ if result:
79
+ # Result is (filename, lineno, name, is_user)
80
+ return (result[0], result[1], result[2])
81
+ return None
82
+ except Exception:
83
+ pass # Fall back to Python
84
+
85
+ # Python fallback
86
+ try:
87
+ code = frame.f_code
88
+ filename = code.co_filename
89
+ if not _is_user_code_py(filename):
90
+ return None
91
+
92
+ return (filename, frame.f_lineno, code.co_name)
93
+ except Exception:
94
+ return None
95
+
96
+
97
+ def is_user_code_func(func) -> bool:
98
+ """
99
+ Quick check if function is user code.
100
+
101
+ Uses C extension if available for ~10x speedup.
102
+ """
103
+ if _FAST_FRAME_OK:
104
+ try:
105
+ return bool(_sfframeinfo.is_user_code_func(func))
106
+ except Exception:
107
+ pass
108
+
109
+ # Python fallback
110
+ try:
111
+ code = getattr(func, "__code__", None)
112
+ if not code:
113
+ return False
114
+ return _is_user_code_py(code.co_filename)
115
+ except Exception:
116
+ return False
@@ -0,0 +1,293 @@
1
+ """
2
+ Ultra-fast network hop sender using C extension with endpoint pre-registration.
3
+ """
4
+
5
+ import os
6
+ from logging import getLogger
7
+ from typing import Optional, Tuple
8
+
9
+ from .regular_data_transmitter import NetworkHopsTransmitter
10
+ from .thread_local import is_network_recording_suppressed
11
+
12
+ logger = getLogger(__name__)
13
+
14
+ # Optional native fast path for network hops (C extension)
15
+ # LAZY IMPORT: Don't import at module level to avoid circular import
16
+ # Import will happen in _ensure_fast_networkhop_initialized() when first needed
17
+ _sfnetworkhop = None
18
+ _NETWORKHOP_FAST_OK = None # None = not yet attempted, True = success, False = failed
19
+
20
+ _FAST_NETWORKHOP_READY = False # one-time guard for native networkhop init
21
+
22
+ # GraphQL mutation string for network hops (with optional body/header capture)
23
+ _COLLECT_NETWORKHOP_MUTATION = (
24
+ "mutation collectNetworkHops("
25
+ "$apiKey: String!,"
26
+ "$sessionId: String!,"
27
+ "$timestampMs: String!,"
28
+ "$line: String!,"
29
+ "$column: String!,"
30
+ "$name: String!,"
31
+ "$entrypoint: String!,"
32
+ "$route: String,"
33
+ "$queryParams: String,"
34
+ "$serviceUuid: String,"
35
+ "$requestHeaders: String,"
36
+ "$requestBody: String,"
37
+ "$responseHeaders: String,"
38
+ "$responseBody: String"
39
+ "){collectNetworkHops("
40
+ "apiKey:$apiKey,sessionId:$sessionId,timestampMs:$timestampMs,"
41
+ "line:$line,column:$column,name:$name,entrypoint:$entrypoint,"
42
+ "route:$route,queryParams:$queryParams,serviceUuid:$serviceUuid,requestHeaders:$requestHeaders,"
43
+ "requestBody:$requestBody,responseHeaders:$responseHeaders,"
44
+ "responseBody:$responseBody)}"
45
+ )
46
+
47
+
48
+ def _ensure_fast_networkhop_initialized() -> bool:
49
+ global _FAST_NETWORKHOP_READY, _sfnetworkhop, _NETWORKHOP_FAST_OK
50
+
51
+ SF_DEBUG = os.getenv("SF_DEBUG", "false").lower() == "true"
52
+
53
+ # PERFORMANCE: Skip network hop extension when testing network library only
54
+ if os.getenv("TESTING_NETWORK_LIBRARY_ONLY", "0") == "1":
55
+ if SF_DEBUG:
56
+ print(
57
+ "[[_ensure_fast_networkhop_initialized]] Network hop extension disabled (TESTING_NETWORK_LIBRARY_ONLY=1)",
58
+ log=False,
59
+ )
60
+ return False
61
+
62
+ # LAZY IMPORT: Try to import C extension on first use
63
+ if _NETWORKHOP_FAST_OK is None:
64
+ try:
65
+ from . import _sfnetworkhop as _sfnh_module
66
+
67
+ _sfnetworkhop = _sfnh_module
68
+ _NETWORKHOP_FAST_OK = True
69
+ if SF_DEBUG:
70
+ print(
71
+ f"[[_ensure_fast_networkhop_initialized]] Successfully imported _sfnetworkhop C extension",
72
+ log=False,
73
+ )
74
+ except Exception as e:
75
+ if SF_DEBUG:
76
+ print(
77
+ f"[[_ensure_fast_networkhop_initialized]] Failed to import _sfnetworkhop C extension: {e}",
78
+ log=False,
79
+ )
80
+ _sfnetworkhop = None
81
+ _NETWORKHOP_FAST_OK = False
82
+
83
+ if not _NETWORKHOP_FAST_OK:
84
+ if SF_DEBUG:
85
+ print(
86
+ f"[[_ensure_fast_networkhop_initialized]] C extension not available (_NETWORKHOP_FAST_OK=False)",
87
+ log=False,
88
+ )
89
+ return False
90
+
91
+ if _FAST_NETWORKHOP_READY:
92
+ if SF_DEBUG:
93
+ print(
94
+ f"[[_ensure_fast_networkhop_initialized]] Already initialized, returning True",
95
+ log=False,
96
+ )
97
+ return True
98
+
99
+ from . import app_config
100
+
101
+ endpoint = getattr(app_config, "_sailfish_graphql_endpoint", None)
102
+ api_key = getattr(app_config, "_sailfish_api_key", None)
103
+ service_uuid = getattr(app_config, "_service_uuid", None)
104
+ http2 = 1 if os.getenv("SF_NBPOST_HTTP2", "0") == "1" else 0
105
+
106
+ if SF_DEBUG:
107
+ print(
108
+ f"[[_ensure_fast_networkhop_initialized]] Config: endpoint={bool(endpoint)}, api_key={bool(api_key)}, service_uuid={bool(service_uuid)}",
109
+ log=False,
110
+ )
111
+
112
+ if not (endpoint and api_key and service_uuid):
113
+ if SF_DEBUG:
114
+ print(
115
+ f"[[_ensure_fast_networkhop_initialized]] Missing required config, returning False",
116
+ log=False,
117
+ )
118
+ return False
119
+
120
+ try:
121
+ if SF_DEBUG:
122
+ print(
123
+ f"[[_ensure_fast_networkhop_initialized]] Calling _sfnetworkhop.init() with url={endpoint}",
124
+ log=False,
125
+ )
126
+ ok = _sfnetworkhop.init(
127
+ url=endpoint,
128
+ query=_COLLECT_NETWORKHOP_MUTATION,
129
+ api_key=str(api_key),
130
+ service_uuid=str(service_uuid),
131
+ http2=http2,
132
+ )
133
+ _FAST_NETWORKHOP_READY = bool(ok)
134
+ if SF_DEBUG:
135
+ print(
136
+ f"[[_ensure_fast_networkhop_initialized]] _sfnetworkhop.init() returned {ok}, _FAST_NETWORKHOP_READY={_FAST_NETWORKHOP_READY}",
137
+ log=False,
138
+ )
139
+ except Exception as e:
140
+ if SF_DEBUG:
141
+ print(
142
+ f"[[_ensure_fast_networkhop_initialized]] _sfnetworkhop.init() raised exception: {e}",
143
+ log=False,
144
+ )
145
+ _FAST_NETWORKHOP_READY = False
146
+
147
+ return _FAST_NETWORKHOP_READY
148
+
149
+
150
+ def register_endpoint(
151
+ line: str, column: str, name: str, entrypoint: str, route: str = None
152
+ ) -> int:
153
+ """
154
+ Register a web framework endpoint's invariant fields once.
155
+ Returns an endpoint_id usable with fast_send_network_hop_fast(...).
156
+
157
+ Args:
158
+ line: Line number where the endpoint is defined
159
+ column: Column number (typically "0")
160
+ name: Function name of the endpoint
161
+ entrypoint: File path where the endpoint is defined
162
+ route: Route pattern (e.g., "/api/users/{id}") - optional
163
+ """
164
+ SF_DEBUG = os.getenv("SF_DEBUG", "false").lower() == "true"
165
+
166
+ if SF_DEBUG:
167
+ print(
168
+ f"[[register_endpoint]] Called for {name} @ {entrypoint}:{line} (route={route})",
169
+ log=False,
170
+ )
171
+
172
+ if not _ensure_fast_networkhop_initialized():
173
+ if SF_DEBUG:
174
+ print(
175
+ f"[[register_endpoint]] _ensure_fast_networkhop_initialized() returned False, returning -1",
176
+ log=False,
177
+ )
178
+ return -1
179
+ try:
180
+ eid = _sfnetworkhop.register_endpoint(
181
+ line=line, column=column, name=name, entrypoint=entrypoint, route=route
182
+ )
183
+ if SF_DEBUG:
184
+ print(
185
+ f"[[register_endpoint]] _sfnetworkhop.register_endpoint() returned {eid}",
186
+ log=False,
187
+ )
188
+ return int(eid)
189
+ except Exception as e:
190
+ if SF_DEBUG:
191
+ print(
192
+ f"[[register_endpoint]] _sfnetworkhop.register_endpoint() raised exception: {e}",
193
+ log=False,
194
+ )
195
+ return -1
196
+
197
+
198
+ def fast_send_network_hop_fast(
199
+ session_id: str,
200
+ endpoint_id: int,
201
+ raw_path: str = None,
202
+ raw_query_string: bytes = None,
203
+ request_headers: dict = None,
204
+ request_body: bytes = None,
205
+ response_headers: dict = None,
206
+ response_body: bytes = None,
207
+ ) -> None:
208
+ """
209
+ ULTRA-FAST PATH: Assumes initialization already happened (checked once at startup).
210
+ Optionally accepts request/response headers and body for capture.
211
+
212
+ Args:
213
+ session_id: Unique session identifier
214
+ endpoint_id: Pre-registered endpoint ID
215
+ raw_path: Optional actual request path (e.g., "/log") - passed to C for JSON escaping
216
+ raw_query_string: Optional raw query string bytes (e.g., b"foo=5") - passed to C for decoding/escaping
217
+ request_headers: Optional dict of request headers
218
+ request_body: Optional request body (bytes, str, or list of chunks)
219
+ response_headers: Optional dict of response headers
220
+ response_body: Optional response body (bytes, str, or list of chunks)
221
+ """
222
+ # Check if network recording is suppressed (e.g., by @skip_network_tracing decorator)
223
+ if is_network_recording_suppressed():
224
+ return
225
+
226
+ # HOT PATH OPTIMIZATION: Skip initialization check after first successful init
227
+ if not _FAST_NETWORKHOP_READY:
228
+ if not _ensure_fast_networkhop_initialized():
229
+ return
230
+
231
+ # Direct C call - no exception handling (C code is bulletproof)
232
+ if endpoint_id >= 0:
233
+ # Check if we need the extended path (with route/query/body/header capture)
234
+ has_extended_data = (
235
+ raw_path is not None
236
+ or raw_query_string is not None
237
+ or request_headers is not None
238
+ or request_body is not None
239
+ or response_headers is not None
240
+ or response_body is not None
241
+ )
242
+
243
+ if has_extended_data:
244
+ # Use extended capture path - C handles all decoding/escaping
245
+ _sfnetworkhop.networkhop_with_bodies(
246
+ session_id=session_id,
247
+ endpoint_id=endpoint_id,
248
+ raw_path=raw_path,
249
+ raw_query_string=raw_query_string,
250
+ request_headers=request_headers,
251
+ request_body=request_body,
252
+ response_headers=response_headers,
253
+ response_body=response_body,
254
+ )
255
+ else:
256
+ # Ultra-fast path with NO extra data
257
+ _sfnetworkhop.networkhop_fast(
258
+ session_id=session_id, endpoint_id=endpoint_id
259
+ )
260
+
261
+
262
+ def fast_send_network_hop(
263
+ session_id: str, line: str, column: str, name: str, entrypoint: str
264
+ ):
265
+ """
266
+ Backward-compatible API (not used by patched FastAPI; kept for other callers).
267
+ """
268
+ # Check if network recording is suppressed (e.g., by @skip_network_tracing decorator)
269
+ if is_network_recording_suppressed():
270
+ return
271
+
272
+ if _ensure_fast_networkhop_initialized():
273
+ try:
274
+ # Old slow path in C; kept for compatibility
275
+ _sfnetworkhop.networkhop(
276
+ session_id=session_id,
277
+ line=line,
278
+ column=column,
279
+ name=name,
280
+ entrypoint=entrypoint,
281
+ )
282
+ return
283
+ except Exception:
284
+ pass
285
+
286
+ # Fallback to Python transmitter
287
+ NetworkHopsTransmitter().send(
288
+ session_id=session_id,
289
+ line=line,
290
+ column=column,
291
+ name=name,
292
+ entrypoint=entrypoint,
293
+ )
@@ -0,0 +1,112 @@
1
+ import sys
2
+ from typing import Any, Iterable, List
3
+
4
+ from .env_vars import (
5
+ SAILFISH_EXCEPTION_LOCALS_HIDE_DUNDER,
6
+ SAILFISH_EXCEPTION_LOCALS_HIDE_SELF,
7
+ SAILFISH_EXCEPTION_LOCALS_HIDE_SUNDER,
8
+ SAILFISH_EXCEPTION_LOCALS_TYPES_TO_IGNORE,
9
+ )
10
+
11
+
12
+ def import_type(module_name: str, type_name: str):
13
+ try:
14
+ exec( # pylint: disable=exec-used
15
+ f"from {module_name} import {type_name}",
16
+ globals(),
17
+ )
18
+ return globals().get(type_name, None)
19
+ except ImportError:
20
+ return None
21
+
22
+
23
+ def get_types_from_str(types_str: str) -> List[type]:
24
+ types_list = []
25
+ if types_str:
26
+ for type_path in types_str.split(","):
27
+ type_path_fixed = type_path.replace(" ", "")
28
+ module_name, type_name = type_path_fixed.rsplit(".", 1)
29
+ type_obj = import_type(module_name, type_name)
30
+ if type_obj:
31
+ types_list.append(type_obj)
32
+ return types_list
33
+
34
+
35
+ EXCEPTION_LOCALS_TYPES_TO_IGNORE = get_types_from_str(
36
+ SAILFISH_EXCEPTION_LOCALS_TYPES_TO_IGNORE
37
+ )
38
+
39
+
40
+ def get_current_frame():
41
+ return sys._getframe(1) # pylint: disable=protected-access
42
+
43
+
44
+ def value_type_to_be_ignored(value: Any) -> bool:
45
+ return any(
46
+ isinstance(value, type_obj) for type_obj in EXCEPTION_LOCALS_TYPES_TO_IGNORE
47
+ )
48
+
49
+
50
+ def key_is_str_to_be_ignored(
51
+ key: Any, locals_hide_self: bool, locals_hide_dunder: bool, locals_hide_sunder: bool
52
+ ) -> bool:
53
+ return (
54
+ (locals_hide_self and key == "self")
55
+ or (locals_hide_dunder and key.startswith("__"))
56
+ or (locals_hide_sunder and key.startswith("_"))
57
+ )
58
+
59
+
60
+ def filter_locals(
61
+ iter_locals: Iterable[tuple[str, object]],
62
+ locals_hide_self: bool,
63
+ locals_hide_dunder: bool,
64
+ locals_hide_sunder: bool,
65
+ ) -> Iterable[tuple[str, object]]:
66
+ for key, value in iter_locals:
67
+ if not isinstance(key, str):
68
+ if value_type_to_be_ignored(value):
69
+ continue
70
+ else:
71
+ if key_is_str_to_be_ignored(
72
+ key, locals_hide_self, locals_hide_dunder, locals_hide_sunder
73
+ ):
74
+ continue
75
+ if value_type_to_be_ignored(value):
76
+ continue
77
+ if key in ("args", "field_args") and isinstance(value, list):
78
+ value = list(
79
+ filter_locals(
80
+ enumerate(value),
81
+ locals_hide_self,
82
+ locals_hide_dunder,
83
+ locals_hide_sunder,
84
+ )
85
+ )
86
+ if key in ("kwargs", "field_kwargs") and isinstance(value, dict):
87
+ value = dict(
88
+ filter_locals(
89
+ value.items(),
90
+ locals_hide_self,
91
+ locals_hide_dunder,
92
+ locals_hide_sunder,
93
+ )
94
+ )
95
+ yield key, value
96
+
97
+
98
+ def get_locals(
99
+ iter_locals: Iterable[tuple[str, object]],
100
+ locals_hide_self: bool = SAILFISH_EXCEPTION_LOCALS_HIDE_SELF,
101
+ locals_hide_dunder: bool = SAILFISH_EXCEPTION_LOCALS_HIDE_DUNDER,
102
+ locals_hide_sunder: bool = SAILFISH_EXCEPTION_LOCALS_HIDE_SUNDER,
103
+ ) -> Iterable[tuple[str, object]]:
104
+ """Extract locals from an iterator of key pairs."""
105
+ if not (locals_hide_dunder or locals_hide_sunder):
106
+ yield from iter_locals
107
+ return
108
+ iter_locals_filtered = filter_locals(
109
+ iter_locals, locals_hide_self, locals_hide_dunder, locals_hide_sunder
110
+ )
111
+ for key, value in iter_locals_filtered:
112
+ yield key, value