turboapi 0.4.13__cp313-cp313-win_amd64.whl → 0.4.15__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.
@@ -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
- def enhanced_handler(**kwargs):
221
- """Enhanced handler with automatic body parsing."""
222
- try:
223
- # If there's a body in kwargs, parse it
224
- if "body" in kwargs:
225
- body_data = kwargs["body"]
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
- if body_data: # Only parse if body is not empty
228
- parsed_body = RequestBodyParser.parse_json_body(
229
- body_data,
230
- sig
231
- )
232
- # Merge parsed body params into kwargs
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
- # Remove the raw body to avoid passing it to handler
236
- kwargs.pop("body", None)
237
-
238
- # Remove headers if present
239
- kwargs.pop("headers", None)
240
-
241
- # Filter kwargs to only pass expected parameters
242
- filtered_kwargs = {
243
- k: v for k, v in kwargs.items()
244
- if k in sig.parameters
245
- }
246
-
247
- # Call original handler
248
- if inspect.iscoroutinefunction(original_handler):
249
- # For async handlers (future support)
250
- result = original_handler(**filtered_kwargs)
251
- else:
252
- result = original_handler(**filtered_kwargs)
253
-
254
- # Normalize response
255
- content, status_code = ResponseHandler.normalize_response(result)
256
-
257
- return ResponseHandler.format_json_response(content, status_code)
258
-
259
- except ValueError as e:
260
- # Validation or parsing error (400 Bad Request)
261
- return ResponseHandler.format_json_response(
262
- {"error": "Bad Request", "detail": str(e)},
263
- 400
264
- )
265
- except Exception as e:
266
- # Unexpected error (500 Internal Server Error)
267
- import traceback
268
- return ResponseHandler.format_json_response(
269
- {
270
- "error": "Internal Server Error",
271
- "detail": str(e),
272
- "traceback": traceback.format_exc()
273
- },
274
- 500
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
- return enhanced_handler
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: turboapi
3
- Version: 0.4.13
3
+ Version: 0.4.15
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -1,5 +1,5 @@
1
- turboapi-0.4.13.dist-info/METADATA,sha256=Cx_c3TzPTzH999Vc5NxdbruBeIuVIISBgN1xXWMVNdM,1482
2
- turboapi-0.4.13.dist-info/WHEEL,sha256=TJQY77QRLvXq32tEs9ATmwKO6NAOtuKOAw50eyiSmWU,96
1
+ turboapi-0.4.15.dist-info/METADATA,sha256=4_skH-GqtS3RKpgwaT13pcN7nNKWCH_6TK84i0A6gQw,1482
2
+ turboapi-0.4.15.dist-info/WHEEL,sha256=TJQY77QRLvXq32tEs9ATmwKO6NAOtuKOAw50eyiSmWU,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=xS9b6HqAGUDRp8D4O09i6jhxmw56771DfvjhRHaMDwU,10752
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.cp313-win_amd64.pyd,sha256=cIBGcGkdVilk6CEYDc_zD5QatdYSiAWOYyNrG9oBFcw,3664384
15
+ turboapi/turbonet.cp313-win_amd64.pyd,sha256=ui6NpZqTnmPAIYUs1Y0BQoCIIWZr-18i-VFSay2dQAk,3688448
16
16
  turboapi/version_check.py,sha256=z3O1vIJsWmG_DO271ayYWSwaDfgpFnfJzYRYyowKYMc,9625
17
- turboapi-0.4.13.dist-info/RECORD,,
17
+ turboapi-0.4.15.dist-info/RECORD,,