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.
- sf_veritas/__init__.py +20 -0
- sf_veritas/_sffastlog.c +889 -0
- sf_veritas/_sffastlog.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnet.c +924 -0
- sf_veritas/_sffastnet.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnetworkrequest.c +730 -0
- sf_veritas/_sffastnetworkrequest.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan.c +2155 -0
- sf_veritas/_sffuncspan.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan_config.c +617 -0
- sf_veritas/_sffuncspan_config.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfheadercheck.c +341 -0
- sf_veritas/_sfheadercheck.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfnetworkhop.c +1451 -0
- sf_veritas/_sfnetworkhop.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfservice.c +1175 -0
- sf_veritas/_sfservice.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfteepreload.c +5167 -0
- sf_veritas/app_config.py +49 -0
- sf_veritas/cli.py +336 -0
- sf_veritas/constants.py +10 -0
- sf_veritas/custom_excepthook.py +304 -0
- sf_veritas/custom_log_handler.py +129 -0
- sf_veritas/custom_output_wrapper.py +144 -0
- sf_veritas/custom_print.py +146 -0
- sf_veritas/django_app.py +5 -0
- sf_veritas/env_vars.py +186 -0
- sf_veritas/exception_handling_middleware.py +18 -0
- sf_veritas/exception_metaclass.py +69 -0
- sf_veritas/fast_frame_info.py +116 -0
- sf_veritas/fast_network_hop.py +293 -0
- sf_veritas/frame_tools.py +112 -0
- sf_veritas/funcspan_config_loader.py +556 -0
- sf_veritas/function_span_profiler.py +1174 -0
- sf_veritas/import_hook.py +62 -0
- sf_veritas/infra_details/__init__.py +3 -0
- sf_veritas/infra_details/get_infra_details.py +24 -0
- sf_veritas/infra_details/kubernetes/__init__.py +3 -0
- sf_veritas/infra_details/kubernetes/get_cluster_name.py +147 -0
- sf_veritas/infra_details/kubernetes/get_details.py +7 -0
- sf_veritas/infra_details/running_on/__init__.py +17 -0
- sf_veritas/infra_details/running_on/kubernetes.py +11 -0
- sf_veritas/interceptors.py +497 -0
- sf_veritas/libsfnettee.so +0 -0
- sf_veritas/local_env_detect.py +118 -0
- sf_veritas/package_metadata.py +6 -0
- sf_veritas/patches/__init__.py +0 -0
- sf_veritas/patches/concurrent_futures.py +19 -0
- sf_veritas/patches/constants.py +1 -0
- sf_veritas/patches/exceptions.py +82 -0
- sf_veritas/patches/multiprocessing.py +32 -0
- sf_veritas/patches/network_libraries/__init__.py +76 -0
- sf_veritas/patches/network_libraries/aiohttp.py +281 -0
- sf_veritas/patches/network_libraries/curl_cffi.py +363 -0
- sf_veritas/patches/network_libraries/http_client.py +419 -0
- sf_veritas/patches/network_libraries/httpcore.py +515 -0
- sf_veritas/patches/network_libraries/httplib2.py +204 -0
- sf_veritas/patches/network_libraries/httpx.py +515 -0
- sf_veritas/patches/network_libraries/niquests.py +211 -0
- sf_veritas/patches/network_libraries/pycurl.py +385 -0
- sf_veritas/patches/network_libraries/requests.py +633 -0
- sf_veritas/patches/network_libraries/tornado.py +341 -0
- sf_veritas/patches/network_libraries/treq.py +270 -0
- sf_veritas/patches/network_libraries/urllib_request.py +468 -0
- sf_veritas/patches/network_libraries/utils.py +398 -0
- sf_veritas/patches/os.py +17 -0
- sf_veritas/patches/threading.py +218 -0
- sf_veritas/patches/web_frameworks/__init__.py +54 -0
- sf_veritas/patches/web_frameworks/aiohttp.py +793 -0
- sf_veritas/patches/web_frameworks/async_websocket_consumer.py +317 -0
- sf_veritas/patches/web_frameworks/blacksheep.py +527 -0
- sf_veritas/patches/web_frameworks/bottle.py +502 -0
- sf_veritas/patches/web_frameworks/cherrypy.py +678 -0
- sf_veritas/patches/web_frameworks/cors_utils.py +122 -0
- sf_veritas/patches/web_frameworks/django.py +944 -0
- sf_veritas/patches/web_frameworks/eve.py +395 -0
- sf_veritas/patches/web_frameworks/falcon.py +926 -0
- sf_veritas/patches/web_frameworks/fastapi.py +724 -0
- sf_veritas/patches/web_frameworks/flask.py +520 -0
- sf_veritas/patches/web_frameworks/klein.py +501 -0
- sf_veritas/patches/web_frameworks/litestar.py +551 -0
- sf_veritas/patches/web_frameworks/pyramid.py +428 -0
- sf_veritas/patches/web_frameworks/quart.py +824 -0
- sf_veritas/patches/web_frameworks/robyn.py +697 -0
- sf_veritas/patches/web_frameworks/sanic.py +857 -0
- sf_veritas/patches/web_frameworks/starlette.py +723 -0
- sf_veritas/patches/web_frameworks/strawberry.py +813 -0
- sf_veritas/patches/web_frameworks/tornado.py +481 -0
- sf_veritas/patches/web_frameworks/utils.py +91 -0
- sf_veritas/print_override.py +13 -0
- sf_veritas/regular_data_transmitter.py +409 -0
- sf_veritas/request_interceptor.py +401 -0
- sf_veritas/request_utils.py +550 -0
- sf_veritas/server_status.py +1 -0
- sf_veritas/shutdown_flag.py +11 -0
- sf_veritas/subprocess_startup.py +3 -0
- sf_veritas/test_cli.py +145 -0
- sf_veritas/thread_local.py +970 -0
- sf_veritas/timeutil.py +114 -0
- sf_veritas/transmit_exception_to_sailfish.py +28 -0
- sf_veritas/transmitter.py +132 -0
- sf_veritas/types.py +47 -0
- sf_veritas/unified_interceptor.py +1580 -0
- sf_veritas/utils.py +39 -0
- sf_veritas-0.10.3.dist-info/METADATA +97 -0
- sf_veritas-0.10.3.dist-info/RECORD +132 -0
- sf_veritas-0.10.3.dist-info/WHEEL +5 -0
- sf_veritas-0.10.3.dist-info/entry_points.txt +2 -0
- sf_veritas-0.10.3.dist-info/top_level.txt +1 -0
- sf_veritas.libs/libbrotlicommon-6ce2a53c.so.1.0.6 +0 -0
- sf_veritas.libs/libbrotlidec-811d1be3.so.1.0.6 +0 -0
- sf_veritas.libs/libcom_err-730ca923.so.2.1 +0 -0
- sf_veritas.libs/libcrypt-52aca757.so.1.1.0 +0 -0
- sf_veritas.libs/libcrypto-bdaed0ea.so.1.1.1k +0 -0
- sf_veritas.libs/libcurl-eaa3cf66.so.4.5.0 +0 -0
- sf_veritas.libs/libgssapi_krb5-323bbd21.so.2.2 +0 -0
- sf_veritas.libs/libidn2-2f4a5893.so.0.3.6 +0 -0
- sf_veritas.libs/libk5crypto-9a74ff38.so.3.1 +0 -0
- sf_veritas.libs/libkeyutils-2777d33d.so.1.6 +0 -0
- sf_veritas.libs/libkrb5-a55300e8.so.3.3 +0 -0
- sf_veritas.libs/libkrb5support-e6594cfc.so.0.1 +0 -0
- sf_veritas.libs/liblber-2-d20824ef.4.so.2.10.9 +0 -0
- sf_veritas.libs/libldap-2-cea2a960.4.so.2.10.9 +0 -0
- sf_veritas.libs/libnghttp2-39367a22.so.14.17.0 +0 -0
- sf_veritas.libs/libpcre2-8-516f4c9d.so.0.7.1 +0 -0
- sf_veritas.libs/libpsl-99becdd3.so.5.3.1 +0 -0
- sf_veritas.libs/libsasl2-7de4d792.so.3.0.0 +0 -0
- sf_veritas.libs/libselinux-d0805dcb.so.1 +0 -0
- sf_veritas.libs/libssh-c11d285b.so.4.8.7 +0 -0
- sf_veritas.libs/libssl-60250281.so.1.1.1k +0 -0
- sf_veritas.libs/libunistring-05abdd40.so.2.1.0 +0 -0
- sf_veritas.libs/libuuid-95b83d40.so.1.3.0 +0 -0
|
@@ -0,0 +1,724 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OTEL-STYLE PURE ASYNC PATTERN:
|
|
3
|
+
• Call C extension directly AFTER response sent
|
|
4
|
+
• C queues to lock-free ring buffer and returns in ~1µs
|
|
5
|
+
• ASGI event loop returns instantly (doesn't wait)
|
|
6
|
+
• C background thread does ALL work with GIL released
|
|
7
|
+
• This should MATCH OTEL performance (identical pattern)
|
|
8
|
+
|
|
9
|
+
KEY INSIGHT: No Python threads! C extension handles everything.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import functools
|
|
14
|
+
import gc
|
|
15
|
+
import json
|
|
16
|
+
from typing import Callable, List, Optional
|
|
17
|
+
|
|
18
|
+
from ... import app_config
|
|
19
|
+
from ...constants import (
|
|
20
|
+
FUNCSPAN_OVERRIDE_HEADER_BYTES,
|
|
21
|
+
SAILFISH_TRACING_HEADER,
|
|
22
|
+
SAILFISH_TRACING_HEADER_BYTES,
|
|
23
|
+
)
|
|
24
|
+
from ...custom_excepthook import custom_excepthook
|
|
25
|
+
from ...env_vars import (
|
|
26
|
+
SF_DEBUG,
|
|
27
|
+
SF_NETWORKHOP_CAPTURE_ENABLED,
|
|
28
|
+
SF_NETWORKHOP_CAPTURE_REQUEST_BODY,
|
|
29
|
+
SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS,
|
|
30
|
+
SF_NETWORKHOP_CAPTURE_RESPONSE_BODY,
|
|
31
|
+
SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS,
|
|
32
|
+
SF_NETWORKHOP_REQUEST_LIMIT_MB,
|
|
33
|
+
SF_NETWORKHOP_RESPONSE_LIMIT_MB,
|
|
34
|
+
)
|
|
35
|
+
from ...fast_network_hop import fast_send_network_hop_fast, register_endpoint
|
|
36
|
+
from ...thread_local import (
|
|
37
|
+
clear_c_tls_parent_trace_id,
|
|
38
|
+
clear_current_request_path,
|
|
39
|
+
clear_outbound_header_base,
|
|
40
|
+
clear_trace_id,
|
|
41
|
+
generate_new_trace_id,
|
|
42
|
+
get_or_set_sf_trace_id,
|
|
43
|
+
get_sf_trace_id,
|
|
44
|
+
set_current_request_path,
|
|
45
|
+
set_funcspan_override,
|
|
46
|
+
set_outbound_header_base,
|
|
47
|
+
)
|
|
48
|
+
from .cors_utils import inject_sailfish_headers, should_inject_headers
|
|
49
|
+
from .utils import _is_user_code, _unwrap_user_func, should_skip_route
|
|
50
|
+
|
|
51
|
+
_SKIP_TRACING_ATTR = "_sf_skip_tracing"
|
|
52
|
+
|
|
53
|
+
# Size limits in bytes
|
|
54
|
+
_REQUEST_LIMIT_BYTES = SF_NETWORKHOP_REQUEST_LIMIT_MB * 1024 * 1024
|
|
55
|
+
_RESPONSE_LIMIT_BYTES = SF_NETWORKHOP_RESPONSE_LIMIT_MB * 1024 * 1024
|
|
56
|
+
|
|
57
|
+
# OTEL-STYLE: No thread pool! Pure async, call C extension directly
|
|
58
|
+
# C extension has its own background worker thread
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
import fastapi
|
|
62
|
+
from starlette.types import ASGIApp, Message, Receive, Scope, Send
|
|
63
|
+
except ImportError:
|
|
64
|
+
|
|
65
|
+
def patch_fastapi(routes_to_skip: Optional[List[str]] = None):
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
else:
|
|
69
|
+
|
|
70
|
+
# Pre-registered endpoint IDs (maps endpoint function id -> endpoint_id from C extension)
|
|
71
|
+
_ENDPOINT_REGISTRY: dict[int, int] = {}
|
|
72
|
+
|
|
73
|
+
# Track which FastAPI app instances have been registered (to support multiple apps)
|
|
74
|
+
_REGISTERED_APPS: set[int] = set()
|
|
75
|
+
|
|
76
|
+
def _truncate_if_needed(data: bytes, limit: int) -> bytes:
|
|
77
|
+
"""Truncate data to limit if needed."""
|
|
78
|
+
if len(data) <= limit:
|
|
79
|
+
return data
|
|
80
|
+
return data[:limit] + b"... [TRUNCATED]"
|
|
81
|
+
|
|
82
|
+
def _serialize_headers(headers: list) -> str:
|
|
83
|
+
"""Serialize headers to JSON string (fast path)."""
|
|
84
|
+
try:
|
|
85
|
+
return json.dumps(
|
|
86
|
+
{k.decode(): v.decode() for k, v in headers}, separators=(",", ":")
|
|
87
|
+
)
|
|
88
|
+
except Exception: # noqa: BLE001
|
|
89
|
+
return "{}"
|
|
90
|
+
|
|
91
|
+
class SFZeroOverheadMiddleware:
|
|
92
|
+
"""ZERO-OVERHEAD network hop capture middleware.
|
|
93
|
+
|
|
94
|
+
OTEL-STYLE PATTERN:
|
|
95
|
+
- Call C extension directly AFTER send completes
|
|
96
|
+
- C queues message and returns in ~1µs (lock-free ring buffer)
|
|
97
|
+
- C background thread does ALL network work with GIL released
|
|
98
|
+
- ASGI event loop never blocks!
|
|
99
|
+
|
|
100
|
+
This matches OTEL's performance because we use the same pattern!
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
def __init__(self, app: ASGIApp):
|
|
104
|
+
self.app = app
|
|
105
|
+
|
|
106
|
+
async def __call__(self, scope: Scope, receive: Receive, send: Send):
|
|
107
|
+
if scope.get("type") != "http":
|
|
108
|
+
await self.app(scope, receive, send)
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
# Set current request path for route-based suppression (SF_DISABLE_INBOUND_NETWORK_TRACING_ON_ROUTES)
|
|
112
|
+
request_path = scope.get("path", "")
|
|
113
|
+
set_current_request_path(request_path)
|
|
114
|
+
|
|
115
|
+
# Always print to verify middleware is being called
|
|
116
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
117
|
+
print(
|
|
118
|
+
f"[[SFZeroOverheadMiddleware.__call__]] HTTP request to {request_path}, type={scope.get('type')}",
|
|
119
|
+
log=False,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# PERFORMANCE: Single-pass bytes-level header scan (no dict allocation until needed)
|
|
123
|
+
# Scan headers once on bytes, only decode what we need, use latin-1 (fast 1:1 byte map)
|
|
124
|
+
hdr_tuples = scope.get("headers") or ()
|
|
125
|
+
incoming_trace_raw = None # bytes
|
|
126
|
+
funcspan_raw = None # bytes
|
|
127
|
+
req_headers = None # dict[str,str] only if capture enabled
|
|
128
|
+
|
|
129
|
+
capture_req_headers = SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS # local cache
|
|
130
|
+
|
|
131
|
+
if capture_req_headers:
|
|
132
|
+
# decode once using latin-1 (1:1 bytes, faster than utf-8 and never throws)
|
|
133
|
+
tmp = {}
|
|
134
|
+
for k, v in hdr_tuples:
|
|
135
|
+
kl = k.lower()
|
|
136
|
+
if kl == SAILFISH_TRACING_HEADER_BYTES:
|
|
137
|
+
incoming_trace_raw = v
|
|
138
|
+
elif kl == FUNCSPAN_OVERRIDE_HEADER_BYTES:
|
|
139
|
+
funcspan_raw = v
|
|
140
|
+
# build the dict while we're here
|
|
141
|
+
tmp[k.decode("latin-1")] = v.decode("latin-1")
|
|
142
|
+
req_headers = tmp
|
|
143
|
+
else:
|
|
144
|
+
for k, v in hdr_tuples:
|
|
145
|
+
kl = k.lower()
|
|
146
|
+
if kl == SAILFISH_TRACING_HEADER_BYTES:
|
|
147
|
+
incoming_trace_raw = v
|
|
148
|
+
elif kl == FUNCSPAN_OVERRIDE_HEADER_BYTES:
|
|
149
|
+
funcspan_raw = v
|
|
150
|
+
# no dict build
|
|
151
|
+
|
|
152
|
+
# CRITICAL: Seed/ensure trace_id immediately (BEFORE any outbound work)
|
|
153
|
+
if incoming_trace_raw:
|
|
154
|
+
# Incoming X-Sf3-Rid header provided - use it
|
|
155
|
+
incoming_trace = incoming_trace_raw.decode("latin-1")
|
|
156
|
+
get_or_set_sf_trace_id(
|
|
157
|
+
incoming_trace, is_associated_with_inbound_request=True
|
|
158
|
+
)
|
|
159
|
+
else:
|
|
160
|
+
# No incoming X-Sf3-Rid header - generate fresh trace_id for this request
|
|
161
|
+
generate_new_trace_id()
|
|
162
|
+
|
|
163
|
+
# Optional funcspan override (decode only if present)
|
|
164
|
+
funcspan_override_header = (
|
|
165
|
+
funcspan_raw.decode("latin-1") if funcspan_raw else None
|
|
166
|
+
)
|
|
167
|
+
if funcspan_override_header:
|
|
168
|
+
try:
|
|
169
|
+
set_funcspan_override(funcspan_override_header)
|
|
170
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
171
|
+
print(
|
|
172
|
+
f"[[SFZeroOverheadMiddleware]] Set function span override from header: {funcspan_override_header}",
|
|
173
|
+
log=False,
|
|
174
|
+
)
|
|
175
|
+
except Exception as e:
|
|
176
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
177
|
+
print(
|
|
178
|
+
f"[[SFZeroOverheadMiddleware]] Failed to set function span override: {e}",
|
|
179
|
+
log=False,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Initialize outbound base without list/allocs from split()
|
|
183
|
+
try:
|
|
184
|
+
trace_id = get_sf_trace_id()
|
|
185
|
+
if trace_id:
|
|
186
|
+
s = str(trace_id)
|
|
187
|
+
i = s.find("/") # session
|
|
188
|
+
j = s.find("/", i + 1) if i != -1 else -1 # page
|
|
189
|
+
if j != -1:
|
|
190
|
+
base_trace = s[:j] # "session/page"
|
|
191
|
+
set_outbound_header_base(
|
|
192
|
+
base_trace=base_trace,
|
|
193
|
+
parent_trace_id=s, # "session/page/uuid"
|
|
194
|
+
funcspan=funcspan_override_header,
|
|
195
|
+
)
|
|
196
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
197
|
+
print(
|
|
198
|
+
f"[[SFZeroOverheadMiddleware]] Initialized outbound header base (base={base_trace[:16]}...)",
|
|
199
|
+
log=False,
|
|
200
|
+
)
|
|
201
|
+
except Exception as e:
|
|
202
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
203
|
+
print(
|
|
204
|
+
f"[[SFZeroOverheadMiddleware]] Failed to initialize outbound header base: {e}",
|
|
205
|
+
log=False,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# OPTIMIZATION: Skip ALL capture infrastructure if not capturing network hops
|
|
209
|
+
# We still needed to set up trace_id and outbound header base above (for outbound call tracing)
|
|
210
|
+
# but we can skip all request/response capture overhead
|
|
211
|
+
if not SF_NETWORKHOP_CAPTURE_ENABLED:
|
|
212
|
+
await self.app(scope, receive, send)
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
# NOTE: req_headers already captured in single-pass scan above (if enabled)
|
|
216
|
+
|
|
217
|
+
# Capture request body if enabled (must intercept receive)
|
|
218
|
+
req_body_chunks = []
|
|
219
|
+
|
|
220
|
+
# OPTIMIZATION: Only wrap receive if we need to capture request body
|
|
221
|
+
if SF_NETWORKHOP_CAPTURE_REQUEST_BODY:
|
|
222
|
+
|
|
223
|
+
async def wrapped_receive():
|
|
224
|
+
message = await receive()
|
|
225
|
+
if message["type"] == "http.request":
|
|
226
|
+
body = message.get("body", b"")
|
|
227
|
+
if body:
|
|
228
|
+
req_body_chunks.append(body)
|
|
229
|
+
return message
|
|
230
|
+
|
|
231
|
+
else:
|
|
232
|
+
wrapped_receive = receive
|
|
233
|
+
|
|
234
|
+
# Capture response headers and body if enabled
|
|
235
|
+
resp_headers = None
|
|
236
|
+
resp_body_chunks = []
|
|
237
|
+
|
|
238
|
+
# OPTIMIZATION: Cache debug flag check (avoid repeated lookups)
|
|
239
|
+
_debug_enabled = SF_DEBUG and app_config._interceptors_initialized
|
|
240
|
+
|
|
241
|
+
# OPTIMIZATION: Cache capture flags (avoid repeated global lookups in hot path)
|
|
242
|
+
_capture_resp_headers = SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS
|
|
243
|
+
_capture_resp_body = SF_NETWORKHOP_CAPTURE_RESPONSE_BODY
|
|
244
|
+
|
|
245
|
+
async def wrapped_send(message: Message):
|
|
246
|
+
nonlocal resp_headers
|
|
247
|
+
|
|
248
|
+
# ULTRA-FAST PATH: Most messages just pass through without any processing
|
|
249
|
+
# Only http.response.body (final) triggers network hop collection
|
|
250
|
+
msg_type = message.get("type")
|
|
251
|
+
|
|
252
|
+
# FAST PATH: Early exit for non-body messages (http.response.start, etc.)
|
|
253
|
+
if msg_type != "http.response.body":
|
|
254
|
+
# Capture response headers if needed (only on http.response.start)
|
|
255
|
+
if _capture_resp_headers and msg_type == "http.response.start":
|
|
256
|
+
try:
|
|
257
|
+
resp_headers = {
|
|
258
|
+
k.decode(): v.decode()
|
|
259
|
+
for k, v in message.get("headers", [])
|
|
260
|
+
}
|
|
261
|
+
except Exception:
|
|
262
|
+
pass
|
|
263
|
+
await send(message)
|
|
264
|
+
return
|
|
265
|
+
|
|
266
|
+
# BODY PATH: Capture body chunks if needed
|
|
267
|
+
if _capture_resp_body:
|
|
268
|
+
body = message.get("body", b"")
|
|
269
|
+
if body:
|
|
270
|
+
resp_body_chunks.append(body)
|
|
271
|
+
|
|
272
|
+
# Send the actual message first
|
|
273
|
+
await send(message)
|
|
274
|
+
|
|
275
|
+
# OPTIMIZATION: Early exit if there's more body chunks coming
|
|
276
|
+
if message.get("more_body", False):
|
|
277
|
+
return
|
|
278
|
+
|
|
279
|
+
# NOW we can get the endpoint (it's been populated by the router)
|
|
280
|
+
endpoint_fn = scope.get("endpoint")
|
|
281
|
+
if not endpoint_fn:
|
|
282
|
+
return # Early exit if no endpoint
|
|
283
|
+
|
|
284
|
+
endpoint_id = _ENDPOINT_REGISTRY.get(id(endpoint_fn))
|
|
285
|
+
if endpoint_id is None or endpoint_id < 0:
|
|
286
|
+
if _debug_enabled:
|
|
287
|
+
print(
|
|
288
|
+
f"[[SFZeroOverheadMiddleware]] Skipping NetworkHop (endpoint_id={endpoint_id}), registry has {len(_ENDPOINT_REGISTRY)} endpoints",
|
|
289
|
+
log=False,
|
|
290
|
+
)
|
|
291
|
+
return # Early exit if invalid endpoint
|
|
292
|
+
|
|
293
|
+
if _debug_enabled:
|
|
294
|
+
print(
|
|
295
|
+
f"[[SFZeroOverheadMiddleware]] Response complete for {scope.get('path')}, endpoint_fn={endpoint_fn.__name__ if hasattr(endpoint_fn, '__name__') else endpoint_fn}, endpoint_id={endpoint_id}",
|
|
296
|
+
log=False,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
# Only proceed if we have valid endpoint
|
|
300
|
+
try:
|
|
301
|
+
# OPTIMIZATION: Use get_sf_trace_id() directly instead of get_or_set_sf_trace_id()
|
|
302
|
+
# Trace ID is GUARANTEED to be set at request start (lines 111-118)
|
|
303
|
+
# This saves ~11-12μs by avoiding tuple unpacking and conditional logic
|
|
304
|
+
session_id = get_sf_trace_id()
|
|
305
|
+
|
|
306
|
+
# OPTIMIZATION: Consolidate body chunks efficiently
|
|
307
|
+
req_body = None
|
|
308
|
+
if req_body_chunks:
|
|
309
|
+
joined = b"".join(req_body_chunks)
|
|
310
|
+
req_body = (
|
|
311
|
+
joined
|
|
312
|
+
if len(joined) <= _REQUEST_LIMIT_BYTES
|
|
313
|
+
else joined[:_REQUEST_LIMIT_BYTES]
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
resp_body = None
|
|
317
|
+
if resp_body_chunks:
|
|
318
|
+
joined = b"".join(resp_body_chunks)
|
|
319
|
+
resp_body = (
|
|
320
|
+
joined
|
|
321
|
+
if len(joined) <= _RESPONSE_LIMIT_BYTES
|
|
322
|
+
else joined[:_RESPONSE_LIMIT_BYTES]
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
# Direct C call - it queues to background worker, returns instantly
|
|
326
|
+
# No need to check SF_NETWORKHOP_CAPTURE_ENABLED - we return early if it's False (line 170)
|
|
327
|
+
fast_send_network_hop_fast(
|
|
328
|
+
session_id=session_id,
|
|
329
|
+
endpoint_id=endpoint_id,
|
|
330
|
+
raw_path=scope["path"],
|
|
331
|
+
raw_query_string=scope["query_string"],
|
|
332
|
+
request_headers=req_headers,
|
|
333
|
+
request_body=req_body,
|
|
334
|
+
response_headers=resp_headers,
|
|
335
|
+
response_body=resp_body,
|
|
336
|
+
)
|
|
337
|
+
if _debug_enabled:
|
|
338
|
+
print(
|
|
339
|
+
f"[[SFZeroOverheadMiddleware]] Emitted NetworkHop for endpoint_id={endpoint_id}",
|
|
340
|
+
log=False,
|
|
341
|
+
)
|
|
342
|
+
except Exception as e: # noqa: BLE001 S110
|
|
343
|
+
if _debug_enabled:
|
|
344
|
+
print(
|
|
345
|
+
f"[[SFZeroOverheadMiddleware]] Failed to emit NetworkHop: {e}",
|
|
346
|
+
log=False,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
try:
|
|
350
|
+
await self.app(scope, wrapped_receive, wrapped_send)
|
|
351
|
+
except Exception as exc: # noqa: BLE001
|
|
352
|
+
custom_excepthook(type(exc), exc, exc.__traceback__)
|
|
353
|
+
raise
|
|
354
|
+
finally:
|
|
355
|
+
# CRITICAL: Clear C TLS to prevent stale data in thread pools
|
|
356
|
+
clear_c_tls_parent_trace_id()
|
|
357
|
+
|
|
358
|
+
# CRITICAL: Clear outbound header base to prevent stale cached headers
|
|
359
|
+
# ContextVar does NOT automatically clean up in thread pools - must clear explicitly
|
|
360
|
+
clear_outbound_header_base()
|
|
361
|
+
|
|
362
|
+
# CRITICAL: Clear trace_id to ensure fresh generation for next request
|
|
363
|
+
# Without this, get_or_set_sf_trace_id() reuses trace_id from previous request
|
|
364
|
+
# causing X-Sf4-Prid to stay constant when no incoming X-Sf3-Rid header
|
|
365
|
+
clear_trace_id()
|
|
366
|
+
|
|
367
|
+
# CRITICAL: Clear current request path to prevent stale data in thread pools
|
|
368
|
+
clear_current_request_path()
|
|
369
|
+
|
|
370
|
+
def _should_trace_endpoint(endpoint_fn: Callable) -> bool:
|
|
371
|
+
"""Check if endpoint should be traced."""
|
|
372
|
+
if getattr(endpoint_fn, _SKIP_TRACING_ATTR, False):
|
|
373
|
+
return False
|
|
374
|
+
|
|
375
|
+
code = getattr(endpoint_fn, "__code__", None)
|
|
376
|
+
if not code:
|
|
377
|
+
return False
|
|
378
|
+
|
|
379
|
+
filename = code.co_filename
|
|
380
|
+
if not _is_user_code(filename):
|
|
381
|
+
return False
|
|
382
|
+
|
|
383
|
+
if getattr(endpoint_fn, "__module__", "").startswith("strawberry"):
|
|
384
|
+
return False
|
|
385
|
+
|
|
386
|
+
return True
|
|
387
|
+
|
|
388
|
+
def _pre_register_endpoints(
|
|
389
|
+
app: "fastapi.FastAPI", routes_to_skip: Optional[List[str]] = None
|
|
390
|
+
):
|
|
391
|
+
"""Pre-register all endpoints at startup."""
|
|
392
|
+
routes_to_skip = routes_to_skip or []
|
|
393
|
+
count = 0
|
|
394
|
+
skipped = 0
|
|
395
|
+
|
|
396
|
+
app_id = id(app)
|
|
397
|
+
|
|
398
|
+
# Check if this app has already been registered
|
|
399
|
+
if app_id in _REGISTERED_APPS:
|
|
400
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
401
|
+
print(
|
|
402
|
+
f"[[_pre_register_endpoints]] App {app_id} already registered, skipping",
|
|
403
|
+
log=False,
|
|
404
|
+
)
|
|
405
|
+
return
|
|
406
|
+
|
|
407
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
408
|
+
print(
|
|
409
|
+
f"[[_pre_register_endpoints]] Starting registration for app {app_id}, app has {len(app.routes)} routes",
|
|
410
|
+
log=False,
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
for route in app.routes:
|
|
414
|
+
if not hasattr(route, "endpoint"):
|
|
415
|
+
skipped += 1
|
|
416
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
417
|
+
print(
|
|
418
|
+
f"[[_pre_register_endpoints]] Skipping route (no endpoint): {route}",
|
|
419
|
+
log=False,
|
|
420
|
+
)
|
|
421
|
+
continue
|
|
422
|
+
|
|
423
|
+
original_endpoint = route.endpoint
|
|
424
|
+
endpoint_fn_id = id(original_endpoint)
|
|
425
|
+
|
|
426
|
+
# Check if this specific endpoint function is already registered
|
|
427
|
+
if endpoint_fn_id in _ENDPOINT_REGISTRY:
|
|
428
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
429
|
+
print(
|
|
430
|
+
f"[[_pre_register_endpoints]] Endpoint function {original_endpoint.__name__ if hasattr(original_endpoint, '__name__') else original_endpoint} already registered (id={_ENDPOINT_REGISTRY[endpoint_fn_id]}), skipping",
|
|
431
|
+
log=False,
|
|
432
|
+
)
|
|
433
|
+
continue
|
|
434
|
+
|
|
435
|
+
# Check for @skip_network_tracing on the wrapped function BEFORE unwrapping
|
|
436
|
+
if getattr(original_endpoint, _SKIP_TRACING_ATTR, False):
|
|
437
|
+
skipped += 1
|
|
438
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
439
|
+
print(
|
|
440
|
+
f"[[_pre_register_endpoints]] Skipping endpoint (marked with @skip_network_tracing): {original_endpoint.__name__ if hasattr(original_endpoint, '__name__') else original_endpoint}",
|
|
441
|
+
log=False,
|
|
442
|
+
)
|
|
443
|
+
continue
|
|
444
|
+
|
|
445
|
+
unwrapped = _unwrap_user_func(original_endpoint)
|
|
446
|
+
|
|
447
|
+
if not _should_trace_endpoint(unwrapped):
|
|
448
|
+
skipped += 1
|
|
449
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
450
|
+
print(
|
|
451
|
+
f"[[_pre_register_endpoints]] Skipping endpoint (not user code): {unwrapped.__name__ if hasattr(unwrapped, '__name__') else unwrapped}",
|
|
452
|
+
log=False,
|
|
453
|
+
)
|
|
454
|
+
continue
|
|
455
|
+
|
|
456
|
+
code = unwrapped.__code__
|
|
457
|
+
line_no_str = str(code.co_firstlineno)
|
|
458
|
+
name = unwrapped.__name__
|
|
459
|
+
filename = code.co_filename
|
|
460
|
+
|
|
461
|
+
# Extract route pattern (e.g., "/log/{n}")
|
|
462
|
+
route_pattern = getattr(route, "path", None)
|
|
463
|
+
|
|
464
|
+
# Check if route should be skipped based on wildcard patterns
|
|
465
|
+
if should_skip_route(route_pattern, routes_to_skip):
|
|
466
|
+
skipped += 1
|
|
467
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
468
|
+
print(
|
|
469
|
+
f"[[_pre_register_endpoints]] Skipping endpoint (route matches skip pattern): {route_pattern}",
|
|
470
|
+
log=False,
|
|
471
|
+
)
|
|
472
|
+
continue
|
|
473
|
+
|
|
474
|
+
endpoint_id = register_endpoint(
|
|
475
|
+
line=line_no_str,
|
|
476
|
+
column="0",
|
|
477
|
+
name=name,
|
|
478
|
+
entrypoint=filename,
|
|
479
|
+
route=route_pattern,
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
if endpoint_id < 0:
|
|
483
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
484
|
+
print(
|
|
485
|
+
f"[[_pre_register_endpoints]] Failed to register {name} (endpoint_id={endpoint_id})",
|
|
486
|
+
log=False,
|
|
487
|
+
)
|
|
488
|
+
continue
|
|
489
|
+
|
|
490
|
+
_ENDPOINT_REGISTRY[endpoint_fn_id] = endpoint_id
|
|
491
|
+
count += 1
|
|
492
|
+
|
|
493
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
494
|
+
print(
|
|
495
|
+
f"[[patch_fastapi]] Registered: {name} @ {filename}:{line_no_str} route={route_pattern} (endpoint_fn_id={endpoint_fn_id}, endpoint_id={endpoint_id})",
|
|
496
|
+
log=False,
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
500
|
+
print(f"[[patch_fastapi]] Total endpoints registered: {count}", log=False)
|
|
501
|
+
|
|
502
|
+
# Only mark this app as registered if we actually registered user endpoints
|
|
503
|
+
# This allows retry on startup event if routes weren't ready yet
|
|
504
|
+
if count > 0:
|
|
505
|
+
_REGISTERED_APPS.add(app_id)
|
|
506
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
507
|
+
print(f"[[patch_fastapi]] App {app_id} marked as registered", log=False)
|
|
508
|
+
print(
|
|
509
|
+
f"[[patch_fastapi]] OTEL-STYLE: Pure async, direct C call (no Python threads)",
|
|
510
|
+
log=False,
|
|
511
|
+
)
|
|
512
|
+
print(
|
|
513
|
+
f"[[patch_fastapi]] Request headers capture: {'ENABLED' if SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS else 'DISABLED'}",
|
|
514
|
+
log=False,
|
|
515
|
+
)
|
|
516
|
+
print(
|
|
517
|
+
f"[[patch_fastapi]] Request body capture: {'ENABLED' if SF_NETWORKHOP_CAPTURE_REQUEST_BODY else 'DISABLED'}",
|
|
518
|
+
log=False,
|
|
519
|
+
)
|
|
520
|
+
print(
|
|
521
|
+
f"[[patch_fastapi]] Response headers capture: {'ENABLED' if SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS else 'DISABLED'}",
|
|
522
|
+
log=False,
|
|
523
|
+
)
|
|
524
|
+
print(
|
|
525
|
+
f"[[patch_fastapi]] Response body capture: {'ENABLED' if SF_NETWORKHOP_CAPTURE_RESPONSE_BODY else 'DISABLED'}",
|
|
526
|
+
log=False,
|
|
527
|
+
)
|
|
528
|
+
print(
|
|
529
|
+
f"[[patch_fastapi]] C extension queues to background worker with GIL released",
|
|
530
|
+
log=False,
|
|
531
|
+
)
|
|
532
|
+
else:
|
|
533
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
534
|
+
print(
|
|
535
|
+
f"[[patch_fastapi]] No user endpoints registered yet for app {app_id}, will retry on startup",
|
|
536
|
+
log=False,
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
def patch_fastapi(routes_to_skip: Optional[List[str]] = None):
|
|
540
|
+
"""
|
|
541
|
+
OTEL-STYLE PURE ASYNC:
|
|
542
|
+
• Direct C call IMMEDIATELY after send completes
|
|
543
|
+
• ASGI returns instantly (C queues and returns in ~1µs)
|
|
544
|
+
• C background thread does all network work with GIL released
|
|
545
|
+
• This is TRUE zero overhead!
|
|
546
|
+
"""
|
|
547
|
+
routes_to_skip = routes_to_skip or []
|
|
548
|
+
|
|
549
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
550
|
+
print("[[patch_fastapi]] Patching FastAPI.__init__", log=False)
|
|
551
|
+
|
|
552
|
+
original_init = fastapi.FastAPI.__init__
|
|
553
|
+
|
|
554
|
+
def patched_init(self, *args, **kwargs):
|
|
555
|
+
original_init(self, *args, **kwargs)
|
|
556
|
+
|
|
557
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
558
|
+
print(
|
|
559
|
+
f"[[patch_fastapi]] FastAPI app created: {self.__class__.__name__}",
|
|
560
|
+
log=False,
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
# Install zero-overhead middleware
|
|
564
|
+
self.add_middleware(SFZeroOverheadMiddleware)
|
|
565
|
+
|
|
566
|
+
# Try to register endpoints immediately if routes are already defined
|
|
567
|
+
# This handles apps where routes are defined before __init__ completes
|
|
568
|
+
if hasattr(self, "routes") and self.routes:
|
|
569
|
+
try:
|
|
570
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
571
|
+
print(
|
|
572
|
+
f"[[patch_fastapi]] Routes already defined ({len(self.routes)} routes), registering immediately",
|
|
573
|
+
log=False,
|
|
574
|
+
)
|
|
575
|
+
_pre_register_endpoints(self, routes_to_skip)
|
|
576
|
+
except Exception as e:
|
|
577
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
578
|
+
print(
|
|
579
|
+
f"[[patch_fastapi]] Immediate registration failed: {e}",
|
|
580
|
+
log=False,
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
# Also register on startup event as a fallback (for routes added after __init__)
|
|
584
|
+
@self.on_event("startup")
|
|
585
|
+
async def _sf_startup():
|
|
586
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
587
|
+
print(
|
|
588
|
+
"[[patch_fastapi]] Startup event fired, registering endpoints",
|
|
589
|
+
log=False,
|
|
590
|
+
)
|
|
591
|
+
# _pre_register_endpoints now checks if this app was already registered
|
|
592
|
+
_pre_register_endpoints(self, routes_to_skip)
|
|
593
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
594
|
+
print(
|
|
595
|
+
"[[patch_fastapi]] ZERO-OVERHEAD pattern activated (truly async, no blocking)",
|
|
596
|
+
log=False,
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
fastapi.FastAPI.__init__ = patched_init
|
|
600
|
+
|
|
601
|
+
# Also patch any existing FastAPI instances that were created before patching
|
|
602
|
+
# This handles the case where app = FastAPI() happens before setup_interceptors()
|
|
603
|
+
for obj in gc.get_objects():
|
|
604
|
+
try:
|
|
605
|
+
# Wrap in try-except to safely handle lazy objects (e.g., Django settings)
|
|
606
|
+
# that trigger initialization on attribute access
|
|
607
|
+
if isinstance(obj, fastapi.FastAPI):
|
|
608
|
+
# Check if this app already has our middleware
|
|
609
|
+
has_our_middleware = any(
|
|
610
|
+
m.__class__.__name__ == "SFZeroOverheadMiddleware"
|
|
611
|
+
for m in getattr(obj, "user_middleware", [])
|
|
612
|
+
)
|
|
613
|
+
if not has_our_middleware:
|
|
614
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
615
|
+
print(
|
|
616
|
+
f"[[patch_fastapi]] Retroactively patching existing FastAPI app",
|
|
617
|
+
log=False,
|
|
618
|
+
)
|
|
619
|
+
obj.add_middleware(SFZeroOverheadMiddleware)
|
|
620
|
+
|
|
621
|
+
# Try immediate registration if routes exist
|
|
622
|
+
if obj.routes:
|
|
623
|
+
try:
|
|
624
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
625
|
+
print(
|
|
626
|
+
f"[[patch_fastapi]] Retroactive immediate registration ({len(obj.routes)} routes)",
|
|
627
|
+
log=False,
|
|
628
|
+
)
|
|
629
|
+
_pre_register_endpoints(obj, routes_to_skip)
|
|
630
|
+
except Exception as e:
|
|
631
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
632
|
+
print(
|
|
633
|
+
f"[[patch_fastapi]] Retroactive immediate registration failed: {e}",
|
|
634
|
+
log=False,
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
@obj.on_event("startup")
|
|
638
|
+
async def _sf_startup_retro():
|
|
639
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
640
|
+
print(
|
|
641
|
+
"[[patch_fastapi]] Retroactive startup event fired",
|
|
642
|
+
log=False,
|
|
643
|
+
)
|
|
644
|
+
# _pre_register_endpoints now checks if this app was already registered
|
|
645
|
+
_pre_register_endpoints(obj, routes_to_skip)
|
|
646
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
647
|
+
print(
|
|
648
|
+
"[[patch_fastapi]] Retroactive registration complete",
|
|
649
|
+
log=False,
|
|
650
|
+
)
|
|
651
|
+
except Exception:
|
|
652
|
+
# Silently skip objects that fail isinstance checks (e.g., Django lazy settings)
|
|
653
|
+
pass
|
|
654
|
+
|
|
655
|
+
def patch_fastapi_cors():
|
|
656
|
+
"""
|
|
657
|
+
Patch Starlette's CORSMiddleware to automatically inject Sailfish headers.
|
|
658
|
+
|
|
659
|
+
SAFE: Only modifies CORS if CORSMiddleware is used by the application.
|
|
660
|
+
This works for both FastAPI and Starlette apps.
|
|
661
|
+
"""
|
|
662
|
+
try:
|
|
663
|
+
from starlette.middleware.cors import CORSMiddleware
|
|
664
|
+
except ImportError:
|
|
665
|
+
# Starlette not installed, skip patching
|
|
666
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
667
|
+
print(
|
|
668
|
+
"[[patch_fastapi_cors]] Starlette CORSMiddleware not found, skipping",
|
|
669
|
+
log=False,
|
|
670
|
+
)
|
|
671
|
+
return
|
|
672
|
+
|
|
673
|
+
# Check if already patched
|
|
674
|
+
if hasattr(CORSMiddleware, "_sf_cors_patched"):
|
|
675
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
676
|
+
print("[[patch_fastapi_cors]] Already patched, skipping", log=False)
|
|
677
|
+
return
|
|
678
|
+
|
|
679
|
+
original_cors_init = CORSMiddleware.__init__
|
|
680
|
+
|
|
681
|
+
def patched_cors_init(
|
|
682
|
+
self,
|
|
683
|
+
app,
|
|
684
|
+
allow_origins=(),
|
|
685
|
+
allow_methods=(),
|
|
686
|
+
allow_headers=(),
|
|
687
|
+
allow_credentials=False,
|
|
688
|
+
allow_origin_regex=None,
|
|
689
|
+
expose_headers=(),
|
|
690
|
+
max_age=600,
|
|
691
|
+
):
|
|
692
|
+
# Intercept allow_headers parameter
|
|
693
|
+
if should_inject_headers(allow_headers):
|
|
694
|
+
allow_headers = inject_sailfish_headers(allow_headers)
|
|
695
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
696
|
+
print(
|
|
697
|
+
"[[patch_fastapi_cors]] Injected Sailfish headers into CORSMiddleware",
|
|
698
|
+
log=False,
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
# Call original init with potentially modified headers
|
|
702
|
+
original_cors_init(
|
|
703
|
+
self,
|
|
704
|
+
app,
|
|
705
|
+
allow_origins=allow_origins,
|
|
706
|
+
allow_methods=allow_methods,
|
|
707
|
+
allow_headers=allow_headers,
|
|
708
|
+
allow_credentials=allow_credentials,
|
|
709
|
+
allow_origin_regex=allow_origin_regex,
|
|
710
|
+
expose_headers=expose_headers,
|
|
711
|
+
max_age=max_age,
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
CORSMiddleware.__init__ = patched_cors_init
|
|
715
|
+
CORSMiddleware._sf_cors_patched = True
|
|
716
|
+
|
|
717
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
718
|
+
print(
|
|
719
|
+
"[[patch_fastapi_cors]] Successfully patched Starlette CORSMiddleware",
|
|
720
|
+
log=False,
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
# Call CORS patching
|
|
724
|
+
patch_fastapi_cors()
|