turboapi 0.3.24__cp313-cp313-win_amd64.whl → 0.3.29__cp313-cp313-win_amd64.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 (30) hide show
  1. turboapi/_rust.cp313-win_amd64.pyd +0 -0
  2. turboapi/async_limiter.py +86 -0
  3. turboapi/async_pool.py +141 -0
  4. turboapi/middleware.py +292 -14
  5. turboapi/request_handler.py +21 -22
  6. turboapi/rust_integration.py +7 -8
  7. turboapi/security.py +542 -0
  8. {turboapi-0.3.24.dist-info → turboapi-0.3.29.dist-info}/METADATA +2 -2
  9. turboapi-0.3.29.dist-info/RECORD +17 -0
  10. {turboapi-0.3.24.dist-info → turboapi-0.3.29.dist-info}/WHEEL +1 -1
  11. turboapi/__pycache__/__init__.cpython-312.pyc +0 -0
  12. turboapi/__pycache__/__init__.cpython-313.pyc +0 -0
  13. turboapi/__pycache__/app.cpython-312.pyc +0 -0
  14. turboapi/__pycache__/app.cpython-313.pyc +0 -0
  15. turboapi/__pycache__/decorators.cpython-312.pyc +0 -0
  16. turboapi/__pycache__/decorators.cpython-313.pyc +0 -0
  17. turboapi/__pycache__/main_app.cpython-312.pyc +0 -0
  18. turboapi/__pycache__/main_app.cpython-313.pyc +0 -0
  19. turboapi/__pycache__/middleware.cpython-312.pyc +0 -0
  20. turboapi/__pycache__/middleware.cpython-313.pyc +0 -0
  21. turboapi/__pycache__/models.cpython-312.pyc +0 -0
  22. turboapi/__pycache__/models.cpython-313.pyc +0 -0
  23. turboapi/__pycache__/routing.cpython-312.pyc +0 -0
  24. turboapi/__pycache__/routing.cpython-313.pyc +0 -0
  25. turboapi/__pycache__/rust_integration.cpython-312.pyc +0 -0
  26. turboapi/__pycache__/rust_integration.cpython-313.pyc +0 -0
  27. turboapi/__pycache__/server_integration.cpython-313.pyc +0 -0
  28. turboapi/__pycache__/version_check.cpython-312.pyc +0 -0
  29. turboapi/__pycache__/version_check.cpython-313.pyc +0 -0
  30. turboapi-0.3.24.dist-info/RECORD +0 -33
Binary file
@@ -0,0 +1,86 @@
1
+ """
2
+ Async Limiter - Semaphore-based rate limiting for async tasks
3
+
4
+ Prevents event loop overload by limiting concurrent async tasks.
5
+ """
6
+
7
+ import asyncio
8
+ from typing import Any, Coroutine
9
+
10
+
11
+ class AsyncLimiter:
12
+ """Semaphore-based limiter for async tasks
13
+
14
+ Limits the number of concurrent async tasks to prevent event loop overload.
15
+ This is critical for maintaining stable performance under high load.
16
+
17
+ Args:
18
+ max_concurrent: Maximum number of concurrent tasks (default: 512)
19
+
20
+ Example:
21
+ limiter = AsyncLimiter(max_concurrent=512)
22
+ result = await limiter(some_coroutine())
23
+ """
24
+
25
+ def __init__(self, max_concurrent: int = 512):
26
+ self.semaphore = asyncio.Semaphore(max_concurrent)
27
+ self.max_concurrent = max_concurrent
28
+ self._active_tasks = 0
29
+
30
+ async def __call__(self, coro: Coroutine) -> Any:
31
+ """Execute coroutine with semaphore gating
32
+
33
+ Args:
34
+ coro: Coroutine to execute
35
+
36
+ Returns:
37
+ Result of the coroutine
38
+ """
39
+ async with self.semaphore:
40
+ self._active_tasks += 1
41
+ try:
42
+ return await coro
43
+ finally:
44
+ self._active_tasks -= 1
45
+
46
+ @property
47
+ def active_tasks(self) -> int:
48
+ """Get current number of active tasks"""
49
+ return self._active_tasks
50
+
51
+ @property
52
+ def available_slots(self) -> int:
53
+ """Get number of available slots"""
54
+ return self.max_concurrent - self._active_tasks
55
+
56
+
57
+ # Global limiter instance per event loop
58
+ _limiters = {}
59
+
60
+
61
+ def get_limiter(max_concurrent: int = 512) -> AsyncLimiter:
62
+ """Get or create limiter for current event loop
63
+
64
+ Args:
65
+ max_concurrent: Maximum concurrent tasks
66
+
67
+ Returns:
68
+ AsyncLimiter instance for current event loop
69
+ """
70
+ try:
71
+ loop = asyncio.get_running_loop()
72
+ loop_id = id(loop)
73
+
74
+ if loop_id not in _limiters:
75
+ _limiters[loop_id] = AsyncLimiter(max_concurrent)
76
+
77
+ return _limiters[loop_id]
78
+ except RuntimeError:
79
+ # No running loop, create standalone limiter
80
+ return AsyncLimiter(max_concurrent)
81
+
82
+
83
+ def reset_limiters():
84
+ """Reset all limiters (useful for testing)"""
85
+ global _limiters
86
+ _limiters = {}
turboapi/async_pool.py ADDED
@@ -0,0 +1,141 @@
1
+ """
2
+ Per-thread asyncio event loop management for Python 3.13+ free-threading.
3
+
4
+ This module provides thread-local event loop management to enable true
5
+ parallel execution of async handlers across multiple threads.
6
+ """
7
+
8
+ import asyncio
9
+ import threading
10
+ from typing import Dict, Optional
11
+ import sys
12
+
13
+
14
+ class EventLoopPool:
15
+ """
16
+ Manages per-thread asyncio event loops for parallel async execution.
17
+
18
+ In Python 3.13+ with free-threading, we can run multiple event loops
19
+ in parallel across different threads without GIL contention.
20
+ """
21
+
22
+ _loops: Dict[int, asyncio.AbstractEventLoop] = {}
23
+ _lock = threading.Lock()
24
+ _initialized = False
25
+
26
+ @classmethod
27
+ def initialize(cls, num_threads: Optional[int] = None) -> None:
28
+ """
29
+ Initialize the event loop pool with the specified number of threads.
30
+
31
+ Args:
32
+ num_threads: Number of threads to create event loops for.
33
+ If None, uses number of CPU cores.
34
+ """
35
+ if cls._initialized:
36
+ return
37
+
38
+ with cls._lock:
39
+ if cls._initialized:
40
+ return
41
+
42
+ if num_threads is None:
43
+ import os
44
+ num_threads = os.cpu_count() or 4
45
+
46
+ print(f"🔄 Initializing EventLoopPool with {num_threads} threads")
47
+ cls._initialized = True
48
+
49
+ @classmethod
50
+ def get_loop_for_thread(cls) -> asyncio.AbstractEventLoop:
51
+ """
52
+ Get or create an event loop for the current thread.
53
+
54
+ Returns:
55
+ The event loop for the current thread.
56
+ """
57
+ thread_id = threading.get_ident()
58
+
59
+ # Fast path: loop already exists
60
+ if thread_id in cls._loops:
61
+ return cls._loops[thread_id]
62
+
63
+ # Slow path: create new loop
64
+ with cls._lock:
65
+ # Double-check after acquiring lock
66
+ if thread_id in cls._loops:
67
+ return cls._loops[thread_id]
68
+
69
+ # Create new event loop for this thread
70
+ loop = asyncio.new_event_loop()
71
+ asyncio.set_event_loop(loop)
72
+ cls._loops[thread_id] = loop
73
+
74
+ print(f"✅ Created event loop for thread {thread_id}")
75
+ return loop
76
+
77
+ @classmethod
78
+ def get_running_loop(cls) -> Optional[asyncio.AbstractEventLoop]:
79
+ """
80
+ Get the running event loop for the current thread, if any.
81
+
82
+ Returns:
83
+ The running event loop, or None if no loop is running.
84
+ """
85
+ try:
86
+ return asyncio.get_running_loop()
87
+ except RuntimeError:
88
+ return None
89
+
90
+ @classmethod
91
+ def cleanup(cls) -> None:
92
+ """Clean up all event loops (call on shutdown)."""
93
+ with cls._lock:
94
+ for thread_id, loop in cls._loops.items():
95
+ if loop.is_running():
96
+ loop.stop()
97
+ loop.close()
98
+ cls._loops.clear()
99
+ cls._initialized = False
100
+
101
+ @classmethod
102
+ def stats(cls) -> Dict[str, int]:
103
+ """Get statistics about the event loop pool."""
104
+ with cls._lock:
105
+ return {
106
+ "total_loops": len(cls._loops),
107
+ "active_threads": len([l for l in cls._loops.values() if l.is_running()]),
108
+ }
109
+
110
+
111
+ def ensure_event_loop() -> asyncio.AbstractEventLoop:
112
+ """
113
+ Ensure an event loop exists for the current thread.
114
+
115
+ This is the primary function to call from Rust to get an event loop.
116
+
117
+ Returns:
118
+ The event loop for the current thread.
119
+ """
120
+ # Try to get running loop first (fast path)
121
+ try:
122
+ return asyncio.get_running_loop()
123
+ except RuntimeError:
124
+ pass
125
+
126
+ # Get or create thread-local loop
127
+ return EventLoopPool.get_loop_for_thread()
128
+
129
+
130
+ # Python 3.13+ free-threading detection
131
+ def is_free_threading_enabled() -> bool:
132
+ """Check if Python 3.13+ free-threading is enabled."""
133
+ return hasattr(sys, '_is_gil_enabled') and not sys._is_gil_enabled()
134
+
135
+
136
+ # Initialize on import
137
+ if is_free_threading_enabled():
138
+ print("🚀 Python 3.13+ free-threading detected - enabling parallel event loops!")
139
+ EventLoopPool.initialize()
140
+ else:
141
+ print("⚠️ Free-threading not enabled - async performance may be limited")
turboapi/middleware.py CHANGED
@@ -1,7 +1,19 @@
1
1
  """
2
- Middleware system for TurboAPI.
2
+ FastAPI-compatible Middleware system for TurboAPI.
3
+
4
+ Includes:
5
+ - CORS (Cross-Origin Resource Sharing)
6
+ - Trusted Host (HTTP Host Header attack prevention)
7
+ - GZip Compression
8
+ - HTTPS Redirect
9
+ - Session Management
10
+ - Custom Middleware Support
3
11
  """
4
12
 
13
+ from typing import List, Optional, Callable, Awaitable, Pattern
14
+ import gzip
15
+ import re
16
+ import time
5
17
  from .models import Request, Response
6
18
 
7
19
 
@@ -25,40 +37,306 @@ class Middleware:
25
37
 
26
38
 
27
39
  class CORSMiddleware(Middleware):
28
- """CORS middleware."""
40
+ """
41
+ CORS (Cross-Origin Resource Sharing) middleware.
42
+
43
+ FastAPI-compatible implementation.
44
+
45
+ Usage:
46
+ app.add_middleware(
47
+ CORSMiddleware,
48
+ allow_origins=["http://localhost:8080"],
49
+ allow_credentials=True,
50
+ allow_methods=["*"],
51
+ allow_headers=["*"],
52
+ )
53
+ """
29
54
 
30
55
  def __init__(
31
56
  self,
32
- allow_origins: list = None,
33
- allow_methods: list = None,
34
- allow_headers: list = None,
35
- allow_credentials: bool = False
57
+ allow_origins: List[str] = None,
58
+ allow_methods: List[str] = None,
59
+ allow_headers: List[str] = None,
60
+ allow_credentials: bool = False,
61
+ allow_origin_regex: Optional[str] = None,
62
+ expose_headers: List[str] = None,
63
+ max_age: int = 600,
36
64
  ):
37
65
  self.allow_origins = allow_origins or ["*"]
38
- self.allow_methods = allow_methods or ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
66
+ self.allow_methods = allow_methods or ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH", "HEAD"]
39
67
  self.allow_headers = allow_headers or ["*"]
40
68
  self.allow_credentials = allow_credentials
69
+ self.allow_origin_regex = re.compile(allow_origin_regex) if allow_origin_regex else None
70
+ self.expose_headers = expose_headers or []
71
+ self.max_age = max_age
72
+
73
+ def before_request(self, request: Request) -> None:
74
+ """Handle preflight OPTIONS requests."""
75
+ if request.method == "OPTIONS":
76
+ # Preflight request
77
+ pass
41
78
 
42
79
  def after_request(self, request: Request, response: Response) -> Response:
43
80
  """Add CORS headers to response."""
44
- response.set_header("Access-Control-Allow-Origin", ",".join(self.allow_origins))
45
- response.set_header("Access-Control-Allow-Methods", ",".join(self.allow_methods))
46
- response.set_header("Access-Control-Allow-Headers", ",".join(self.allow_headers))
47
-
81
+ origin = request.headers.get("origin", "")
82
+
83
+ # Check if origin is allowed
84
+ if self.allow_origin_regex and self.allow_origin_regex.match(origin):
85
+ response.set_header("Access-Control-Allow-Origin", origin)
86
+ elif "*" in self.allow_origins:
87
+ response.set_header("Access-Control-Allow-Origin", "*")
88
+ elif origin in self.allow_origins:
89
+ response.set_header("Access-Control-Allow-Origin", origin)
90
+
91
+ response.set_header("Access-Control-Allow-Methods", ", ".join(self.allow_methods))
92
+ response.set_header("Access-Control-Allow-Headers", ", ".join(self.allow_headers))
93
+
94
+ if self.expose_headers:
95
+ response.set_header("Access-Control-Expose-Headers", ", ".join(self.expose_headers))
96
+
48
97
  if self.allow_credentials:
49
98
  response.set_header("Access-Control-Allow-Credentials", "true")
99
+
100
+ response.set_header("Access-Control-Max-Age", str(self.max_age))
101
+
102
+ return response
50
103
 
104
+
105
+ class TrustedHostMiddleware(Middleware):
106
+ """
107
+ Trusted Host middleware - prevents HTTP Host Header attacks.
108
+
109
+ FastAPI-compatible implementation.
110
+
111
+ Usage:
112
+ app.add_middleware(
113
+ TrustedHostMiddleware,
114
+ allowed_hosts=["example.com", "*.example.com"]
115
+ )
116
+ """
117
+
118
+ def __init__(
119
+ self,
120
+ allowed_hosts: List[str] = None,
121
+ www_redirect: bool = True,
122
+ ):
123
+ if allowed_hosts is None:
124
+ allowed_hosts = ["*"]
125
+
126
+ self.allowed_hosts = allowed_hosts
127
+ self.www_redirect = www_redirect
128
+
129
+ # Compile regex patterns for wildcard hosts
130
+ self.allowed_host_patterns = []
131
+ for host in allowed_hosts:
132
+ if host == "*":
133
+ self.allowed_host_patterns.append(re.compile(".*"))
134
+ else:
135
+ # Convert wildcard to regex
136
+ pattern = host.replace(".", r"\.").replace("*", ".*")
137
+ self.allowed_host_patterns.append(re.compile(f"^{pattern}$"))
138
+
139
+ def before_request(self, request: Request) -> None:
140
+ """Validate Host header."""
141
+ host = request.headers.get("host", "").split(":")[0]
142
+
143
+ # Check if host is allowed
144
+ if not any(pattern.match(host) for pattern in self.allowed_host_patterns):
145
+ raise Exception(f"Invalid host header: {host}")
146
+
147
+
148
+ class GZipMiddleware(Middleware):
149
+ """
150
+ GZip compression middleware.
151
+
152
+ FastAPI-compatible implementation.
153
+
154
+ Usage:
155
+ app.add_middleware(GZipMiddleware, minimum_size=1000)
156
+ """
157
+
158
+ def __init__(
159
+ self,
160
+ minimum_size: int = 500,
161
+ compresslevel: int = 9,
162
+ ):
163
+ self.minimum_size = minimum_size
164
+ self.compresslevel = compresslevel
165
+
166
+ def after_request(self, request: Request, response: Response) -> Response:
167
+ """Compress response if client accepts gzip."""
168
+ accept_encoding = request.headers.get("accept-encoding", "")
169
+
170
+ if "gzip" not in accept_encoding.lower():
171
+ return response
172
+
173
+ # Check if response is large enough to compress
174
+ if hasattr(response, 'content'):
175
+ content = response.content
176
+ if isinstance(content, str):
177
+ content = content.encode('utf-8')
178
+
179
+ if len(content) < self.minimum_size:
180
+ return response
181
+
182
+ # Compress content
183
+ compressed = gzip.compress(content, compresslevel=self.compresslevel)
184
+ response.content = compressed
185
+ response.set_header("Content-Encoding", "gzip")
186
+ response.set_header("Content-Length", str(len(compressed)))
187
+ response.set_header("Vary", "Accept-Encoding")
188
+
51
189
  return response
52
190
 
53
191
 
192
+ class HTTPSRedirectMiddleware(Middleware):
193
+ """
194
+ HTTPS redirect middleware - redirects HTTP to HTTPS.
195
+
196
+ FastAPI-compatible implementation.
197
+
198
+ Usage:
199
+ app.add_middleware(HTTPSRedirectMiddleware)
200
+ """
201
+
202
+ def before_request(self, request: Request) -> None:
203
+ """Redirect HTTP to HTTPS."""
204
+ # Check if request is HTTP
205
+ scheme = request.headers.get("x-forwarded-proto", "http")
206
+ if scheme == "http":
207
+ # Redirect to HTTPS
208
+ https_url = f"https://{request.headers.get('host', '')}{request.path}"
209
+ if request.query_string:
210
+ https_url += f"?{request.query_string}"
211
+
212
+ raise HTTPSRedirect(https_url)
213
+
214
+
215
+ class HTTPSRedirect(Exception):
216
+ """Exception to trigger HTTPS redirect."""
217
+ def __init__(self, url: str):
218
+ self.url = url
219
+
220
+
221
+ class SessionMiddleware(Middleware):
222
+ """
223
+ Session management middleware.
224
+
225
+ Usage:
226
+ app.add_middleware(
227
+ SessionMiddleware,
228
+ secret_key="your-secret-key-here",
229
+ session_cookie="session"
230
+ )
231
+ """
232
+
233
+ def __init__(
234
+ self,
235
+ secret_key: str,
236
+ session_cookie: str = "session",
237
+ max_age: int = 14 * 24 * 60 * 60, # 14 days
238
+ same_site: str = "lax",
239
+ https_only: bool = False,
240
+ ):
241
+ self.secret_key = secret_key
242
+ self.session_cookie = session_cookie
243
+ self.max_age = max_age
244
+ self.same_site = same_site
245
+ self.https_only = https_only
246
+
247
+ def before_request(self, request: Request) -> None:
248
+ """Load session from cookie."""
249
+ # TODO: Implement session loading
250
+ request.session = {}
251
+
252
+ def after_request(self, request: Request, response: Response) -> Response:
253
+ """Save session to cookie."""
254
+ # TODO: Implement session saving
255
+ return response
256
+
257
+
258
+ class RateLimitMiddleware(Middleware):
259
+ """
260
+ Rate limiting middleware.
261
+
262
+ Usage:
263
+ app.add_middleware(
264
+ RateLimitMiddleware,
265
+ requests_per_minute=60
266
+ )
267
+ """
268
+
269
+ def __init__(
270
+ self,
271
+ requests_per_minute: int = 60,
272
+ burst: int = 10,
273
+ ):
274
+ self.requests_per_minute = requests_per_minute
275
+ self.burst = burst
276
+ self.requests = {} # IP -> [(timestamp, count)]
277
+
278
+ def before_request(self, request: Request) -> None:
279
+ """Check rate limit."""
280
+ client_ip = request.headers.get("x-forwarded-for", "unknown").split(",")[0].strip()
281
+ now = time.time()
282
+
283
+ # Clean old requests
284
+ if client_ip in self.requests:
285
+ self.requests[client_ip] = [
286
+ (ts, count) for ts, count in self.requests[client_ip]
287
+ if now - ts < 60
288
+ ]
289
+
290
+ # Count requests in last minute
291
+ if client_ip not in self.requests:
292
+ self.requests[client_ip] = []
293
+
294
+ request_count = sum(count for _, count in self.requests[client_ip])
295
+
296
+ if request_count >= self.requests_per_minute:
297
+ raise Exception("Rate limit exceeded")
298
+
299
+ # Add this request
300
+ self.requests[client_ip].append((now, 1))
301
+
302
+
54
303
  class LoggingMiddleware(Middleware):
55
- """Request logging middleware."""
304
+ """
305
+ Request logging middleware.
306
+
307
+ Usage:
308
+ app.add_middleware(LoggingMiddleware)
309
+ """
56
310
 
57
311
  def before_request(self, request: Request) -> None:
58
312
  """Log incoming request."""
313
+ request._start_time = time.time()
59
314
  print(f"[REQUEST] {request.method} {request.path}")
60
315
 
61
316
  def after_request(self, request: Request, response: Response) -> Response:
62
- """Log response."""
63
- print(f"[RESPONSE] {request.method} {request.path} -> {response.status_code}")
317
+ """Log response with timing."""
318
+ duration = time.time() - getattr(request, '_start_time', time.time())
319
+ print(f"[RESPONSE] {request.method} {request.path} -> {response.status_code} ({duration*1000:.2f}ms)")
64
320
  return response
321
+
322
+
323
+ class CustomMiddleware(Middleware):
324
+ """
325
+ Custom middleware wrapper for function-based middleware.
326
+
327
+ Usage:
328
+ @app.middleware("http")
329
+ async def add_process_time_header(request, call_next):
330
+ start_time = time.time()
331
+ response = await call_next(request)
332
+ process_time = time.time() - start_time
333
+ response.headers["X-Process-Time"] = str(process_time)
334
+ return response
335
+ """
336
+
337
+ def __init__(self, func: Callable):
338
+ self.func = func
339
+
340
+ async def __call__(self, request: Request, call_next: Callable) -> Response:
341
+ """Execute custom middleware function."""
342
+ return await self.func(request, call_next)
@@ -191,38 +191,37 @@ def create_enhanced_handler(original_handler, route_definition):
191
191
  # Filter kwargs to only pass expected parameters
192
192
  filtered_kwargs = {
193
193
  k: v for k, v in kwargs.items()
194
+ if k in sig.parameters
194
195
  }
195
196
 
196
197
  # Call original handler
197
- # v0.3.21: Async handlers are now supported via Rust's tokio runtime!
198
- # The Rust layer (server.rs) will detect coroutines and await them properly
199
- # using pyo3-async-runtimes, giving us native async performance
200
- result = original_handler(**filtered_kwargs)
201
-
202
- # Check if result is a coroutine - if so, return it directly for Rust to await
203
- import inspect
204
- if inspect.iscoroutine(result):
205
- # Return coroutine directly - Rust will await it using tokio
206
- return result
198
+ if inspect.iscoroutinefunction(original_handler):
199
+ # For async handlers (future support)
200
+ result = original_handler(**filtered_kwargs)
201
+ else:
202
+ result = original_handler(**filtered_kwargs)
207
203
 
208
- # Sync result - normalize and return as JSON string
204
+ # Normalize response
209
205
  content, status_code = ResponseHandler.normalize_response(result)
210
206
 
211
- # Return JSON string directly for Rust to use
212
- import json
213
- return json.dumps(content)
207
+ return ResponseHandler.format_json_response(content, status_code)
208
+
214
209
  except ValueError as e:
215
210
  # Validation or parsing error (400 Bad Request)
216
- import json
217
- return json.dumps({"error": "Bad Request", "detail": str(e)})
211
+ return ResponseHandler.format_json_response(
212
+ {"error": "Bad Request", "detail": str(e)},
213
+ 400
214
+ )
218
215
  except Exception as e:
219
216
  # Unexpected error (500 Internal Server Error)
220
217
  import traceback
221
- import json
222
- return json.dumps({
223
- "error": "Internal Server Error",
224
- "detail": str(e),
225
- "traceback": traceback.format_exc()
226
- })
218
+ return ResponseHandler.format_json_response(
219
+ {
220
+ "error": "Internal Server Error",
221
+ "detail": str(e),
222
+ "traceback": traceback.format_exc()
223
+ },
224
+ 500
225
+ )
227
226
 
228
227
  return enhanced_handler
@@ -12,7 +12,7 @@ from .request_handler import create_enhanced_handler, ResponseHandler
12
12
  from .version_check import CHECK_MARK, CROSS_MARK, ROCKET
13
13
 
14
14
  try:
15
- from turboapi import _rust as turbonet
15
+ import turbonet
16
16
  RUST_CORE_AVAILABLE = True
17
17
  except ImportError:
18
18
  RUST_CORE_AVAILABLE = False
@@ -175,6 +175,7 @@ class RustIntegratedTurboAPI(TurboAPI):
175
175
 
176
176
  # Add query parameters
177
177
  call_args.update(query_params)
178
+
178
179
  # Always add body and headers for enhanced handler
179
180
  call_args['body'] = body if body else b''
180
181
  call_args['headers'] = headers
@@ -186,7 +187,7 @@ class RustIntegratedTurboAPI(TurboAPI):
186
187
  # {"content": ..., "status_code": ..., "content_type": ...}
187
188
  # But Rust expects a plain dict that it will JSON serialize
188
189
  # So just return the content directly
189
- if isinstance(result, dict) and 'content' in result:
190
+ if isinstance(result, dict) and 'content' in result and 'status_code' in result:
190
191
  # Return just the content - Rust will handle status codes later
191
192
  # For now, just return the content as a dict
192
193
  return result['content']
@@ -205,15 +206,13 @@ class RustIntegratedTurboAPI(TurboAPI):
205
206
 
206
207
  return rust_handler # noqa: B023
207
208
 
208
- # Create and register the handler
209
- handler_func = create_rust_handler(enhanced_handler, route)
210
- rust_handler = handler_func
211
-
212
- # Register with Rust server
209
+ # Register the ORIGINAL handler directly with Rust
210
+ # Rust will call it with call0() (no arguments)
211
+ # The original handler doesn't expect any arguments
213
212
  self.rust_server.add_route(
214
213
  route.method.value,
215
214
  route.path,
216
- rust_handler
215
+ route.handler # Pass original handler, not wrapper!
217
216
  )
218
217
 
219
218
  print(f"{CHECK_MARK} Registered {route.method.value} {route.path} with Rust server")