turboapi 0.4.12__cp313-cp313-win_amd64.whl → 0.4.13__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.
@@ -18,6 +18,11 @@ class RequestBodyParser:
18
18
  """
19
19
  Parse JSON body and extract parameters for handler.
20
20
 
21
+ Supports multiple patterns:
22
+ 1. Single parameter (dict/list/Model) - receives entire body
23
+ 2. Multiple parameters - extracts fields from JSON
24
+ 3. Satya Model - validates entire body
25
+
21
26
  Args:
22
27
  body: Raw request body bytes
23
28
  handler_signature: Signature of the handler function
@@ -34,9 +39,42 @@ class RequestBodyParser:
34
39
  raise ValueError(f"Invalid JSON body: {e}")
35
40
 
36
41
  parsed_params = {}
42
+ params_list = list(handler_signature.parameters.items())
43
+
44
+ # PATTERN 1: Single parameter that should receive entire body
45
+ # Examples: handler(data: dict), handler(items: list), handler(request: Model)
46
+ if len(params_list) == 1:
47
+ param_name, param = params_list[0]
48
+
49
+ # Check if parameter is a Satya Model
50
+ try:
51
+ is_satya_model = inspect.isclass(param.annotation) and issubclass(param.annotation, Model)
52
+ except Exception:
53
+ is_satya_model = False
54
+
55
+ if is_satya_model:
56
+ # Validate entire JSON body against Satya model
57
+ try:
58
+ validated_model = param.annotation.model_validate(json_data)
59
+ parsed_params[param_name] = validated_model
60
+ return parsed_params
61
+ except Exception as e:
62
+ raise ValueError(f"Validation error for {param_name}: {e}")
63
+
64
+ # If annotated as dict or list, pass entire body
65
+ elif param.annotation in (dict, list) or param.annotation == inspect.Parameter.empty:
66
+ parsed_params[param_name] = json_data
67
+ return parsed_params
68
+
69
+ # Check for typing.Dict, typing.List, etc.
70
+ origin = get_origin(param.annotation)
71
+ if origin in (dict, list):
72
+ parsed_params[param_name] = json_data
73
+ return parsed_params
37
74
 
38
- # Check each parameter in the handler signature
39
- for param_name, param in handler_signature.parameters.items():
75
+ # PATTERN 2: Multiple parameters - extract individual fields
76
+ # Example: handler(name: str, age: int, email: str)
77
+ for param_name, param in params_list:
40
78
  if param.annotation == inspect.Parameter.empty:
41
79
  # No type annotation, try to match by name
42
80
  if param_name in json_data:
@@ -138,9 +176,21 @@ class ResponseHandler:
138
176
  if isinstance(content, Model):
139
177
  content = content.model_dump()
140
178
 
141
- # Ensure content is JSON-serializable
142
- if not isinstance(content, (dict, list, str, int, float, bool, type(None))):
143
- content = str(content)
179
+ # Recursively convert any nested Satya models in dicts/lists
180
+ def make_serializable(obj):
181
+ if isinstance(obj, Model):
182
+ return obj.model_dump()
183
+ elif isinstance(obj, dict):
184
+ return {k: make_serializable(v) for k, v in obj.items()}
185
+ elif isinstance(obj, (list, tuple)):
186
+ return [make_serializable(item) for item in obj]
187
+ elif isinstance(obj, (str, int, float, bool, type(None))):
188
+ return obj
189
+ else:
190
+ # Try to convert to string for unknown types
191
+ return str(obj)
192
+
193
+ content = make_serializable(content)
144
194
 
145
195
  return {
146
196
  "content": content,
@@ -128,91 +128,14 @@ class RustIntegratedTurboAPI(TurboAPI):
128
128
  # Create enhanced handler with automatic body parsing
129
129
  enhanced_handler = create_enhanced_handler(route.handler, route)
130
130
 
131
- # Create Rust-compatible handler wrapper
132
- def create_rust_handler(python_handler, route_def):
133
- def rust_handler(rust_request):
134
- """Rust-callable handler that calls Python function with automatic body parsing."""
135
- try:
136
- # Extract request data from Rust
137
- path = rust_request.path
138
- query_string = rust_request.query_string
139
-
140
- # Get headers - try method call first, then attribute
141
- try:
142
- headers = rust_request.get_headers() if callable(getattr(rust_request, 'get_headers', None)) else {}
143
- except:
144
- headers = getattr(rust_request, 'headers', {})
145
-
146
- # Get body - Rust sets it as 'body' attribute (bytes)
147
- body = getattr(rust_request, 'body', b'')
148
-
149
- # Also try get_body if it's set
150
- if not body:
151
- get_body_attr = getattr(rust_request, 'get_body', None)
152
- if get_body_attr is not None:
153
- if callable(get_body_attr):
154
- body = get_body_attr()
155
- else:
156
- body = get_body_attr
157
-
158
- # Parse query parameters
159
- query_params = {}
160
- if query_string:
161
- # Simple query string parsing
162
- for param in query_string.split('&'):
163
- if '=' in param:
164
- key, value = param.split('=', 1)
165
- query_params[key] = value
166
-
167
- # Parse path parameters
168
- path_params = self._extract_path_params(route_def.path, path)
169
-
170
- # Prepare arguments for enhanced handler
171
- call_args = {}
172
-
173
- # Add path parameters
174
- call_args.update(path_params)
175
-
176
- # Add query parameters
177
- call_args.update(query_params)
178
-
179
- # Always add body and headers for enhanced handler
180
- call_args['body'] = body if body else b''
181
- call_args['headers'] = headers
182
-
183
- # Call enhanced handler (handles parsing, validation, response normalization)
184
- result = python_handler(**call_args)
185
-
186
- # Enhanced handler returns normalized format
187
- # {"content": ..., "status_code": ..., "content_type": ...}
188
- # But Rust expects a plain dict that it will JSON serialize
189
- # So just return the content directly
190
- if isinstance(result, dict) and 'content' in result and 'status_code' in result:
191
- # Return just the content - Rust will handle status codes later
192
- # For now, just return the content as a dict
193
- return result['content']
194
-
195
- # Fallback for plain dict responses
196
- return result
197
-
198
- except Exception as e:
199
- # Return 500 error as plain dict (Rust will serialize it)
200
- import traceback
201
- return {
202
- "error": "Internal Server Error",
203
- "detail": str(e),
204
- "traceback": traceback.format_exc()
205
- }
206
-
207
- return rust_handler # noqa: B023
208
-
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
131
+ # For now, just register the original handler
132
+ # TODO: Implement request data passing from Rust to enable enhanced handler
133
+ # The Rust server currently calls handlers with call0() (no arguments)
134
+ # We need to modify the Rust server to pass request data
212
135
  self.rust_server.add_route(
213
136
  route.method.value,
214
137
  route.path,
215
- route.handler # Pass original handler, not wrapper!
138
+ enhanced_handler # Register enhanced handler directly
216
139
  )
217
140
 
218
141
  print(f"{CHECK_MARK} Registered {route.method.value} {route.path} with Rust server")
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: turboapi
3
- Version: 0.4.12
3
+ Version: 0.4.13
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.12.dist-info/METADATA,sha256=IBt2GTnjmkVpSOn-PzMpMzXtwdB4jcqesqC2Bvj64_E,1482
2
- turboapi-0.4.12.dist-info/WHEEL,sha256=TJQY77QRLvXq32tEs9ATmwKO6NAOtuKOAw50eyiSmWU,96
1
+ turboapi-0.4.13.dist-info/METADATA,sha256=Cx_c3TzPTzH999Vc5NxdbruBeIuVIISBgN1xXWMVNdM,1482
2
+ turboapi-0.4.13.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=KrN9d3r7bO8LUU68X6cXTtl3a2dCoRqmdWrjDW2V2qQ,8413
10
+ turboapi/request_handler.py,sha256=xS9b6HqAGUDRp8D4O09i6jhxmw56771DfvjhRHaMDwU,10752
11
11
  turboapi/routing.py,sha256=iCbty56a2J9qnCtxIHQtYf66ZoKVxgISxwCxYvGmgEs,7746
12
- turboapi/rust_integration.py,sha256=AsdB14odDYHFcMNlKeef0Dh8uqE0lfEqcC4MjSld0tM,14930
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=M6HuhjYQyUeuzyvYqViyuoyRzmQBIJ6JDls8uZAejms,3659264
15
+ turboapi/turbonet.cp313-win_amd64.pyd,sha256=cIBGcGkdVilk6CEYDc_zD5QatdYSiAWOYyNrG9oBFcw,3664384
16
16
  turboapi/version_check.py,sha256=z3O1vIJsWmG_DO271ayYWSwaDfgpFnfJzYRYyowKYMc,9625
17
- turboapi-0.4.12.dist-info/RECORD,,
17
+ turboapi-0.4.13.dist-info/RECORD,,