turboapi 0.4.13__cp314-cp314-win_amd64.whl → 0.4.15__cp314-cp314-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.
turboapi/request_handler.py
CHANGED
|
@@ -1,15 +1,107 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Enhanced Request Handler with Satya Integration
|
|
3
3
|
Provides FastAPI-compatible automatic JSON body parsing and validation
|
|
4
|
+
Supports query parameters, path parameters, headers, and request body
|
|
4
5
|
"""
|
|
5
6
|
|
|
6
7
|
import inspect
|
|
7
8
|
import json
|
|
9
|
+
import urllib.parse
|
|
8
10
|
from typing import Any, get_args, get_origin
|
|
9
11
|
|
|
10
12
|
from satya import Model
|
|
11
13
|
|
|
12
14
|
|
|
15
|
+
class QueryParamParser:
|
|
16
|
+
"""Parse query parameters from query string."""
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def parse_query_params(query_string: str) -> dict[str, Any]:
|
|
20
|
+
"""
|
|
21
|
+
Parse query string into dict of parameters.
|
|
22
|
+
Supports multiple values for same key (returns list).
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
query_string: URL query string (e.g., "q=test&limit=10")
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Dictionary of parsed query parameters
|
|
29
|
+
"""
|
|
30
|
+
if not query_string:
|
|
31
|
+
return {}
|
|
32
|
+
|
|
33
|
+
params = {}
|
|
34
|
+
parsed = urllib.parse.parse_qs(query_string, keep_blank_values=True)
|
|
35
|
+
|
|
36
|
+
for key, values in parsed.items():
|
|
37
|
+
# If only one value, return as string; otherwise return as list
|
|
38
|
+
if len(values) == 1:
|
|
39
|
+
params[key] = values[0]
|
|
40
|
+
else:
|
|
41
|
+
params[key] = values
|
|
42
|
+
|
|
43
|
+
return params
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class PathParamParser:
|
|
47
|
+
"""Parse path parameters from URL path."""
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def extract_path_params(route_pattern: str, actual_path: str) -> dict[str, str]:
|
|
51
|
+
"""
|
|
52
|
+
Extract path parameters from actual path using route pattern.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
route_pattern: Route pattern with {param} placeholders (e.g., "/users/{user_id}")
|
|
56
|
+
actual_path: Actual request path (e.g., "/users/123")
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Dictionary of extracted path parameters
|
|
60
|
+
"""
|
|
61
|
+
import re
|
|
62
|
+
|
|
63
|
+
# Convert route pattern to regex
|
|
64
|
+
# Replace {param} with named capture groups
|
|
65
|
+
pattern = re.sub(r'\{(\w+)\}', r'(?P<\1>[^/]+)', route_pattern)
|
|
66
|
+
pattern = f'^{pattern}$'
|
|
67
|
+
|
|
68
|
+
match = re.match(pattern, actual_path)
|
|
69
|
+
if match:
|
|
70
|
+
return match.groupdict()
|
|
71
|
+
|
|
72
|
+
return {}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class HeaderParser:
|
|
76
|
+
"""Parse and extract headers from request."""
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def parse_headers(headers_dict: dict[str, str], handler_signature: inspect.Signature) -> dict[str, Any]:
|
|
80
|
+
"""
|
|
81
|
+
Parse headers and extract parameters needed by handler.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
headers_dict: Dictionary of request headers
|
|
85
|
+
handler_signature: Signature of the handler function
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Dictionary of parsed header parameters
|
|
89
|
+
"""
|
|
90
|
+
parsed_headers = {}
|
|
91
|
+
|
|
92
|
+
# Check each parameter in handler signature
|
|
93
|
+
for param_name, param in handler_signature.parameters.items():
|
|
94
|
+
# Check if parameter name matches a header (case-insensitive)
|
|
95
|
+
header_key = param_name.replace('_', '-').lower()
|
|
96
|
+
|
|
97
|
+
for header_name, header_value in headers_dict.items():
|
|
98
|
+
if header_name.lower() == header_key:
|
|
99
|
+
parsed_headers[param_name] = header_value
|
|
100
|
+
break
|
|
101
|
+
|
|
102
|
+
return parsed_headers
|
|
103
|
+
|
|
104
|
+
|
|
13
105
|
class RequestBodyParser:
|
|
14
106
|
"""Parse and validate request bodies using Satya models."""
|
|
15
107
|
|
|
@@ -207,71 +299,164 @@ def create_enhanced_handler(original_handler, route_definition):
|
|
|
207
299
|
1. Parses JSON body automatically using Satya validation
|
|
208
300
|
2. Normalizes responses (supports tuple returns)
|
|
209
301
|
3. Provides better error messages
|
|
302
|
+
4. Properly handles both sync and async handlers
|
|
210
303
|
|
|
211
304
|
Args:
|
|
212
305
|
original_handler: The original Python handler function
|
|
213
306
|
route_definition: RouteDefinition with metadata
|
|
214
307
|
|
|
215
308
|
Returns:
|
|
216
|
-
Enhanced handler function
|
|
309
|
+
Enhanced handler function (async if original is async, sync otherwise)
|
|
217
310
|
"""
|
|
218
311
|
sig = inspect.signature(original_handler)
|
|
312
|
+
is_async = inspect.iscoroutinefunction(original_handler)
|
|
219
313
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
314
|
+
if is_async:
|
|
315
|
+
# Create async enhanced handler for async original handlers
|
|
316
|
+
async def enhanced_handler(**kwargs):
|
|
317
|
+
"""Enhanced handler with automatic parsing of body, query params, path params, and headers."""
|
|
318
|
+
try:
|
|
319
|
+
parsed_params = {}
|
|
226
320
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
kwargs.update(parsed_body)
|
|
321
|
+
# 1. Parse query parameters
|
|
322
|
+
if "query_string" in kwargs:
|
|
323
|
+
query_string = kwargs.get("query_string", "")
|
|
324
|
+
if query_string:
|
|
325
|
+
query_params = QueryParamParser.parse_query_params(query_string)
|
|
326
|
+
parsed_params.update(query_params)
|
|
234
327
|
|
|
235
|
-
#
|
|
236
|
-
kwargs
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
328
|
+
# 2. Parse path parameters (if route pattern is available)
|
|
329
|
+
if "path" in kwargs and hasattr(route_definition, 'path'):
|
|
330
|
+
actual_path = kwargs.get("path", "")
|
|
331
|
+
route_pattern = route_definition.path
|
|
332
|
+
if actual_path and route_pattern:
|
|
333
|
+
path_params = PathParamParser.extract_path_params(route_pattern, actual_path)
|
|
334
|
+
parsed_params.update(path_params)
|
|
335
|
+
|
|
336
|
+
# 3. Parse headers
|
|
337
|
+
if "headers" in kwargs:
|
|
338
|
+
headers_dict = kwargs.get("headers", {})
|
|
339
|
+
if headers_dict:
|
|
340
|
+
header_params = HeaderParser.parse_headers(headers_dict, sig)
|
|
341
|
+
parsed_params.update(header_params)
|
|
342
|
+
|
|
343
|
+
# 4. Parse request body (JSON)
|
|
344
|
+
if "body" in kwargs:
|
|
345
|
+
body_data = kwargs["body"]
|
|
346
|
+
|
|
347
|
+
if body_data: # Only parse if body is not empty
|
|
348
|
+
parsed_body = RequestBodyParser.parse_json_body(
|
|
349
|
+
body_data,
|
|
350
|
+
sig
|
|
351
|
+
)
|
|
352
|
+
# Merge parsed body params (body params take precedence)
|
|
353
|
+
parsed_params.update(parsed_body)
|
|
354
|
+
|
|
355
|
+
# Filter to only pass expected parameters
|
|
356
|
+
filtered_kwargs = {
|
|
357
|
+
k: v for k, v in parsed_params.items()
|
|
358
|
+
if k in sig.parameters
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
# Call original async handler and await it
|
|
362
|
+
result = await original_handler(**filtered_kwargs)
|
|
363
|
+
|
|
364
|
+
# Normalize response
|
|
365
|
+
content, status_code = ResponseHandler.normalize_response(result)
|
|
366
|
+
|
|
367
|
+
return ResponseHandler.format_json_response(content, status_code)
|
|
368
|
+
|
|
369
|
+
except ValueError as e:
|
|
370
|
+
# Validation or parsing error (400 Bad Request)
|
|
371
|
+
return ResponseHandler.format_json_response(
|
|
372
|
+
{"error": "Bad Request", "detail": str(e)},
|
|
373
|
+
400
|
|
374
|
+
)
|
|
375
|
+
except Exception as e:
|
|
376
|
+
# Unexpected error (500 Internal Server Error)
|
|
377
|
+
import traceback
|
|
378
|
+
return ResponseHandler.format_json_response(
|
|
379
|
+
{
|
|
380
|
+
"error": "Internal Server Error",
|
|
381
|
+
"detail": str(e),
|
|
382
|
+
"traceback": traceback.format_exc()
|
|
383
|
+
},
|
|
384
|
+
500
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
return enhanced_handler
|
|
276
388
|
|
|
277
|
-
|
|
389
|
+
else:
|
|
390
|
+
# Create sync enhanced handler for sync original handlers
|
|
391
|
+
def enhanced_handler(**kwargs):
|
|
392
|
+
"""Enhanced handler with automatic parsing of body, query params, path params, and headers."""
|
|
393
|
+
try:
|
|
394
|
+
parsed_params = {}
|
|
395
|
+
|
|
396
|
+
# 1. Parse query parameters
|
|
397
|
+
if "query_string" in kwargs:
|
|
398
|
+
query_string = kwargs.get("query_string", "")
|
|
399
|
+
if query_string:
|
|
400
|
+
query_params = QueryParamParser.parse_query_params(query_string)
|
|
401
|
+
parsed_params.update(query_params)
|
|
402
|
+
|
|
403
|
+
# 2. Parse path parameters (if route pattern is available)
|
|
404
|
+
if "path" in kwargs and hasattr(route_definition, 'path'):
|
|
405
|
+
actual_path = kwargs.get("path", "")
|
|
406
|
+
route_pattern = route_definition.path
|
|
407
|
+
if actual_path and route_pattern:
|
|
408
|
+
path_params = PathParamParser.extract_path_params(route_pattern, actual_path)
|
|
409
|
+
parsed_params.update(path_params)
|
|
410
|
+
|
|
411
|
+
# 3. Parse headers
|
|
412
|
+
if "headers" in kwargs:
|
|
413
|
+
headers_dict = kwargs.get("headers", {})
|
|
414
|
+
if headers_dict:
|
|
415
|
+
header_params = HeaderParser.parse_headers(headers_dict, sig)
|
|
416
|
+
parsed_params.update(header_params)
|
|
417
|
+
|
|
418
|
+
# 4. Parse request body (JSON)
|
|
419
|
+
if "body" in kwargs:
|
|
420
|
+
body_data = kwargs["body"]
|
|
421
|
+
|
|
422
|
+
if body_data: # Only parse if body is not empty
|
|
423
|
+
parsed_body = RequestBodyParser.parse_json_body(
|
|
424
|
+
body_data,
|
|
425
|
+
sig
|
|
426
|
+
)
|
|
427
|
+
# Merge parsed body params (body params take precedence)
|
|
428
|
+
parsed_params.update(parsed_body)
|
|
429
|
+
|
|
430
|
+
# Filter to only pass expected parameters
|
|
431
|
+
filtered_kwargs = {
|
|
432
|
+
k: v for k, v in parsed_params.items()
|
|
433
|
+
if k in sig.parameters
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
# Call original sync handler
|
|
437
|
+
result = original_handler(**filtered_kwargs)
|
|
438
|
+
|
|
439
|
+
# Normalize response
|
|
440
|
+
content, status_code = ResponseHandler.normalize_response(result)
|
|
441
|
+
|
|
442
|
+
return ResponseHandler.format_json_response(content, status_code)
|
|
443
|
+
|
|
444
|
+
except ValueError as e:
|
|
445
|
+
# Validation or parsing error (400 Bad Request)
|
|
446
|
+
return ResponseHandler.format_json_response(
|
|
447
|
+
{"error": "Bad Request", "detail": str(e)},
|
|
448
|
+
400
|
|
449
|
+
)
|
|
450
|
+
except Exception as e:
|
|
451
|
+
# Unexpected error (500 Internal Server Error)
|
|
452
|
+
import traceback
|
|
453
|
+
return ResponseHandler.format_json_response(
|
|
454
|
+
{
|
|
455
|
+
"error": "Internal Server Error",
|
|
456
|
+
"detail": str(e),
|
|
457
|
+
"traceback": traceback.format_exc()
|
|
458
|
+
},
|
|
459
|
+
500
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
return enhanced_handler
|
|
Binary file
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
turboapi-0.4.
|
|
2
|
-
turboapi-0.4.
|
|
1
|
+
turboapi-0.4.15.dist-info/METADATA,sha256=4_skH-GqtS3RKpgwaT13pcN7nNKWCH_6TK84i0A6gQw,1482
|
|
2
|
+
turboapi-0.4.15.dist-info/WHEEL,sha256=tZ3VAZ5HuUzziFCJ2lDsDJnJO-xy4omAQIa7TJCFCZk,96
|
|
3
3
|
turboapi/__init__.py,sha256=r9Fphtu9ruHFUhSpBMAGxY5en2wvcnsE1nMp2DDRM6w,692
|
|
4
4
|
turboapi/async_limiter.py,sha256=x2qkloPbg2YelDNUXKya2BwBTq5zVxDHxuaQspIgYBg,2416
|
|
5
5
|
turboapi/async_pool.py,sha256=UVm0A-0jIN4V43jY8a5XEU_L0SSyWGMV2bs5FiQGr2M,4489
|
|
@@ -7,11 +7,11 @@ turboapi/decorators.py,sha256=jjJrIXZ3y_yJ231ar24hS09OCDtTqmYA7arpIOcr2kk,1788
|
|
|
7
7
|
turboapi/main_app.py,sha256=_rH5xUahFvyqk8Y9O4rs7v5m1q4_AbkBHitg04KL6O4,11678
|
|
8
8
|
turboapi/middleware.py,sha256=iqtklH5_GMICuAmmxMBfaFSNZkR8wHSNbwhNscGe-pA,11200
|
|
9
9
|
turboapi/models.py,sha256=VCU68f9MGtDdFb4crsx2e0SHghICg8zjU8OumfdpZLQ,5363
|
|
10
|
-
turboapi/request_handler.py,sha256=
|
|
10
|
+
turboapi/request_handler.py,sha256=ymAuJushPEyUUMpyolTp70cn3mvjSF3Et5hPtdMoZYk,18642
|
|
11
11
|
turboapi/routing.py,sha256=iCbty56a2J9qnCtxIHQtYf66ZoKVxgISxwCxYvGmgEs,7746
|
|
12
12
|
turboapi/rust_integration.py,sha256=d1xqC8cX8kehDlRuDQhVbMPw-cQysmI2ZR-j29JD8GA,10676
|
|
13
13
|
turboapi/security.py,sha256=-XgwBhiqQZdfU7oKLHi-3xN_UwlKiQxpfSQ6kTA0ko8,17230
|
|
14
14
|
turboapi/server_integration.py,sha256=drUhhTasWgQfyhFiAaHKd987N3mnE0qkMab1ylmqd4c,18340
|
|
15
|
-
turboapi/turbonet.cp314-win_amd64.pyd,sha256=
|
|
15
|
+
turboapi/turbonet.cp314-win_amd64.pyd,sha256=GF_oeKq8Lm8dU_LJpqvq51Jo5n_uWzuPdp5GB8f-DrY,3681280
|
|
16
16
|
turboapi/version_check.py,sha256=z3O1vIJsWmG_DO271ayYWSwaDfgpFnfJzYRYyowKYMc,9625
|
|
17
|
-
turboapi-0.4.
|
|
17
|
+
turboapi-0.4.15.dist-info/RECORD,,
|
|
File without changes
|