tetra-rp 0.6.0__py3-none-any.whl → 0.24.0__py3-none-any.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 (97) hide show
  1. tetra_rp/__init__.py +109 -19
  2. tetra_rp/cli/commands/__init__.py +1 -0
  3. tetra_rp/cli/commands/apps.py +143 -0
  4. tetra_rp/cli/commands/build.py +1082 -0
  5. tetra_rp/cli/commands/build_utils/__init__.py +1 -0
  6. tetra_rp/cli/commands/build_utils/handler_generator.py +176 -0
  7. tetra_rp/cli/commands/build_utils/lb_handler_generator.py +309 -0
  8. tetra_rp/cli/commands/build_utils/manifest.py +430 -0
  9. tetra_rp/cli/commands/build_utils/mothership_handler_generator.py +75 -0
  10. tetra_rp/cli/commands/build_utils/scanner.py +596 -0
  11. tetra_rp/cli/commands/deploy.py +580 -0
  12. tetra_rp/cli/commands/init.py +123 -0
  13. tetra_rp/cli/commands/resource.py +108 -0
  14. tetra_rp/cli/commands/run.py +296 -0
  15. tetra_rp/cli/commands/test_mothership.py +458 -0
  16. tetra_rp/cli/commands/undeploy.py +533 -0
  17. tetra_rp/cli/main.py +97 -0
  18. tetra_rp/cli/utils/__init__.py +1 -0
  19. tetra_rp/cli/utils/app.py +15 -0
  20. tetra_rp/cli/utils/conda.py +127 -0
  21. tetra_rp/cli/utils/deployment.py +530 -0
  22. tetra_rp/cli/utils/ignore.py +143 -0
  23. tetra_rp/cli/utils/skeleton.py +184 -0
  24. tetra_rp/cli/utils/skeleton_template/.env.example +4 -0
  25. tetra_rp/cli/utils/skeleton_template/.flashignore +40 -0
  26. tetra_rp/cli/utils/skeleton_template/.gitignore +44 -0
  27. tetra_rp/cli/utils/skeleton_template/README.md +263 -0
  28. tetra_rp/cli/utils/skeleton_template/main.py +44 -0
  29. tetra_rp/cli/utils/skeleton_template/mothership.py +55 -0
  30. tetra_rp/cli/utils/skeleton_template/pyproject.toml +58 -0
  31. tetra_rp/cli/utils/skeleton_template/requirements.txt +1 -0
  32. tetra_rp/cli/utils/skeleton_template/workers/__init__.py +0 -0
  33. tetra_rp/cli/utils/skeleton_template/workers/cpu/__init__.py +19 -0
  34. tetra_rp/cli/utils/skeleton_template/workers/cpu/endpoint.py +36 -0
  35. tetra_rp/cli/utils/skeleton_template/workers/gpu/__init__.py +19 -0
  36. tetra_rp/cli/utils/skeleton_template/workers/gpu/endpoint.py +61 -0
  37. tetra_rp/client.py +136 -33
  38. tetra_rp/config.py +29 -0
  39. tetra_rp/core/api/runpod.py +591 -39
  40. tetra_rp/core/deployment.py +232 -0
  41. tetra_rp/core/discovery.py +425 -0
  42. tetra_rp/core/exceptions.py +50 -0
  43. tetra_rp/core/resources/__init__.py +27 -9
  44. tetra_rp/core/resources/app.py +738 -0
  45. tetra_rp/core/resources/base.py +139 -4
  46. tetra_rp/core/resources/constants.py +21 -0
  47. tetra_rp/core/resources/cpu.py +115 -13
  48. tetra_rp/core/resources/gpu.py +182 -16
  49. tetra_rp/core/resources/live_serverless.py +153 -16
  50. tetra_rp/core/resources/load_balancer_sls_resource.py +440 -0
  51. tetra_rp/core/resources/network_volume.py +126 -31
  52. tetra_rp/core/resources/resource_manager.py +436 -35
  53. tetra_rp/core/resources/serverless.py +537 -120
  54. tetra_rp/core/resources/serverless_cpu.py +201 -0
  55. tetra_rp/core/resources/template.py +1 -59
  56. tetra_rp/core/utils/constants.py +10 -0
  57. tetra_rp/core/utils/file_lock.py +260 -0
  58. tetra_rp/core/utils/http.py +67 -0
  59. tetra_rp/core/utils/lru_cache.py +75 -0
  60. tetra_rp/core/utils/singleton.py +36 -1
  61. tetra_rp/core/validation.py +44 -0
  62. tetra_rp/execute_class.py +301 -0
  63. tetra_rp/protos/remote_execution.py +98 -9
  64. tetra_rp/runtime/__init__.py +1 -0
  65. tetra_rp/runtime/circuit_breaker.py +274 -0
  66. tetra_rp/runtime/config.py +12 -0
  67. tetra_rp/runtime/exceptions.py +49 -0
  68. tetra_rp/runtime/generic_handler.py +206 -0
  69. tetra_rp/runtime/lb_handler.py +189 -0
  70. tetra_rp/runtime/load_balancer.py +160 -0
  71. tetra_rp/runtime/manifest_fetcher.py +192 -0
  72. tetra_rp/runtime/metrics.py +325 -0
  73. tetra_rp/runtime/models.py +73 -0
  74. tetra_rp/runtime/mothership_provisioner.py +512 -0
  75. tetra_rp/runtime/production_wrapper.py +266 -0
  76. tetra_rp/runtime/reliability_config.py +149 -0
  77. tetra_rp/runtime/retry_manager.py +118 -0
  78. tetra_rp/runtime/serialization.py +124 -0
  79. tetra_rp/runtime/service_registry.py +346 -0
  80. tetra_rp/runtime/state_manager_client.py +248 -0
  81. tetra_rp/stubs/live_serverless.py +35 -17
  82. tetra_rp/stubs/load_balancer_sls.py +357 -0
  83. tetra_rp/stubs/registry.py +145 -19
  84. {tetra_rp-0.6.0.dist-info → tetra_rp-0.24.0.dist-info}/METADATA +398 -60
  85. tetra_rp-0.24.0.dist-info/RECORD +99 -0
  86. {tetra_rp-0.6.0.dist-info → tetra_rp-0.24.0.dist-info}/WHEEL +1 -1
  87. tetra_rp-0.24.0.dist-info/entry_points.txt +2 -0
  88. tetra_rp/core/pool/cluster_manager.py +0 -177
  89. tetra_rp/core/pool/dataclass.py +0 -18
  90. tetra_rp/core/pool/ex.py +0 -38
  91. tetra_rp/core/pool/job.py +0 -22
  92. tetra_rp/core/pool/worker.py +0 -19
  93. tetra_rp/core/resources/utils.py +0 -50
  94. tetra_rp/core/utils/json.py +0 -33
  95. tetra_rp-0.6.0.dist-info/RECORD +0 -39
  96. /tetra_rp/{core/pool → cli}/__init__.py +0 -0
  97. {tetra_rp-0.6.0.dist-info → tetra_rp-0.24.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,357 @@
1
+ """LoadBalancerSlsStub - Stub for load-balanced serverless execution.
2
+
3
+ Enables @remote decorator to work with LoadBalancerSlsResource endpoints
4
+ via direct HTTP calls instead of queue-based job submission.
5
+ """
6
+
7
+ import inspect
8
+ import logging
9
+ from typing import Any, Callable, Dict, List, Optional
10
+
11
+ import httpx
12
+
13
+ from tetra_rp.core.utils.http import get_authenticated_httpx_client
14
+ from tetra_rp.runtime.serialization import (
15
+ deserialize_arg,
16
+ serialize_args,
17
+ serialize_kwargs,
18
+ )
19
+ from .live_serverless import get_function_source
20
+
21
+ log = logging.getLogger(__name__)
22
+
23
+
24
+ class LoadBalancerSlsStub:
25
+ """HTTP-based stub for load-balanced serverless endpoint execution.
26
+
27
+ Implements the stub interface for @remote decorator with LoadBalancerSlsResource,
28
+ providing direct HTTP-based function execution instead of queue-based processing.
29
+
30
+ Key differences from LiveServerlessStub:
31
+ - Direct HTTP POST to /execute endpoint (not queue-based)
32
+ - No job ID polling - synchronous HTTP response
33
+ - Same function serialization pattern (cloudpickle + base64)
34
+ - Lower latency but no automatic retries
35
+
36
+ Architecture:
37
+ 1. User calls @remote decorated function
38
+ 2. Decorator dispatches to this stub via singledispatch
39
+ 3. Stub serializes function code and arguments
40
+ 4. Stub POSTs to endpoint /execute with serialized data
41
+ 5. Endpoint deserializes, executes, and returns result
42
+ 6. Stub deserializes result and returns to user
43
+
44
+ Example:
45
+ stub = LoadBalancerSlsStub(lb_resource)
46
+ result = await stub(my_func, deps, sys_deps, accel, arg1, arg2)
47
+ """
48
+
49
+ DEFAULT_TIMEOUT = 30.0 # Default timeout in seconds
50
+
51
+ def __init__(self, server: Any, timeout: Optional[float] = None) -> None:
52
+ """Initialize stub with LoadBalancerSlsResource server.
53
+
54
+ Args:
55
+ server: LoadBalancerSlsResource instance with endpoint_url configured
56
+ timeout: Request timeout in seconds (default: 30.0)
57
+ """
58
+ self.server = server
59
+ self.timeout = timeout if timeout is not None else self.DEFAULT_TIMEOUT
60
+
61
+ def _should_use_execute_endpoint(self, func: Callable[..., Any]) -> bool:
62
+ """Determine if /execute endpoint should be used for this function.
63
+
64
+ The /execute endpoint (which accepts arbitrary function code) is only used for:
65
+ - LiveLoadBalancer (local development)
66
+ - Functions without routing metadata (backward compatibility)
67
+
68
+ For deployed LoadBalancerSlsResource endpoints with routing metadata,
69
+ the stub translates @remote calls into HTTP requests to user-defined routes.
70
+
71
+ Args:
72
+ func: Function being called
73
+
74
+ Returns:
75
+ True if /execute should be used, False if user route should be used
76
+ """
77
+ from ..core.resources.live_serverless import LiveLoadBalancer
78
+
79
+ # Always use /execute for LiveLoadBalancer (local development)
80
+ if isinstance(self.server, LiveLoadBalancer):
81
+ log.debug(f"Using /execute endpoint for LiveLoadBalancer: {func.__name__}")
82
+ return True
83
+
84
+ # Check if function has routing metadata
85
+ routing_config = getattr(func, "__remote_config__", None)
86
+ if not routing_config:
87
+ log.debug(f"No routing config for {func.__name__}, using /execute fallback")
88
+ return True
89
+
90
+ # Check if routing metadata is complete
91
+ if not routing_config.get("method") or not routing_config.get("path"):
92
+ log.debug(
93
+ f"Incomplete routing config for {func.__name__}, using /execute fallback"
94
+ )
95
+ return True
96
+
97
+ # Use user-defined route for deployed endpoints with complete routing metadata
98
+ log.debug(
99
+ f"Using user route for deployed endpoint: {func.__name__} "
100
+ f"{routing_config['method']} {routing_config['path']}"
101
+ )
102
+ return False
103
+
104
+ async def __call__(
105
+ self,
106
+ func: Callable[..., Any],
107
+ dependencies: Optional[List[str]],
108
+ system_dependencies: Optional[List[str]],
109
+ accelerate_downloads: bool,
110
+ *args: Any,
111
+ **kwargs: Any,
112
+ ) -> Any:
113
+ """Execute function on load-balanced endpoint.
114
+
115
+ Behavior depends on endpoint type:
116
+ - LiveLoadBalancer: Uses /execute endpoint (local development)
117
+ - Deployed LoadBalancerSlsResource: Uses user-defined route via HTTP
118
+
119
+ Args:
120
+ func: Function to execute
121
+ dependencies: Pip dependencies required
122
+ system_dependencies: System dependencies required
123
+ accelerate_downloads: Whether to accelerate downloads
124
+ *args: Function positional arguments
125
+ **kwargs: Function keyword arguments
126
+
127
+ Returns:
128
+ Function result
129
+
130
+ Raises:
131
+ Exception: If endpoint returns error or HTTP call fails
132
+ """
133
+ # Determine execution path based on resource type and routing metadata
134
+ if self._should_use_execute_endpoint(func):
135
+ # Local development or backward compatibility: use /execute endpoint
136
+ request = self._prepare_request(
137
+ func,
138
+ dependencies,
139
+ system_dependencies,
140
+ accelerate_downloads,
141
+ *args,
142
+ **kwargs,
143
+ )
144
+ response = await self._execute_function(request)
145
+ return self._handle_response(response)
146
+ else:
147
+ # Deployed endpoint: use user-defined route
148
+ routing_config = func.__remote_config__
149
+ return await self._execute_via_user_route(
150
+ func,
151
+ routing_config["method"],
152
+ routing_config["path"],
153
+ *args,
154
+ **kwargs,
155
+ )
156
+
157
+ def _prepare_request(
158
+ self,
159
+ func: Callable[..., Any],
160
+ dependencies: Optional[List[str]],
161
+ system_dependencies: Optional[List[str]],
162
+ accelerate_downloads: bool,
163
+ *args: Any,
164
+ **kwargs: Any,
165
+ ) -> Dict[str, Any]:
166
+ """Prepare HTTP request payload.
167
+
168
+ Extracts function source code and serializes arguments using cloudpickle.
169
+
170
+ Args:
171
+ func: Function to serialize
172
+ dependencies: Pip dependencies
173
+ system_dependencies: System dependencies
174
+ accelerate_downloads: Download acceleration flag
175
+ *args: Function arguments
176
+ **kwargs: Function keyword arguments
177
+
178
+ Returns:
179
+ Request dictionary with serialized function and arguments
180
+ """
181
+ source, _ = get_function_source(func)
182
+ log.debug(f"Extracted source for {func.__name__} ({len(source)} bytes)")
183
+
184
+ request = {
185
+ "function_name": func.__name__,
186
+ "function_code": source,
187
+ "dependencies": dependencies or [],
188
+ "system_dependencies": system_dependencies or [],
189
+ "accelerate_downloads": accelerate_downloads,
190
+ }
191
+
192
+ # Serialize arguments using cloudpickle + base64
193
+ if args:
194
+ request["args"] = serialize_args(args)
195
+ log.debug(f"Serialized {len(args)} positional args for {func.__name__}")
196
+
197
+ if kwargs:
198
+ request["kwargs"] = serialize_kwargs(kwargs)
199
+ log.debug(f"Serialized {len(kwargs)} keyword args for {func.__name__}")
200
+
201
+ return request
202
+
203
+ async def _execute_function(self, request: Dict[str, Any]) -> Dict[str, Any]:
204
+ """Execute function via direct HTTP POST to endpoint.
205
+
206
+ Posts serialized function and arguments to /execute endpoint.
207
+ No job ID polling - waits for synchronous HTTP response.
208
+
209
+ Args:
210
+ request: Request dictionary with function_code, args, kwargs
211
+
212
+ Returns:
213
+ Response dictionary with success flag and result
214
+
215
+ Raises:
216
+ httpx.HTTPError: If HTTP request fails
217
+ ValueError: If endpoint_url not available
218
+ """
219
+ if not self.server.endpoint_url:
220
+ raise ValueError(
221
+ "Endpoint URL not available - endpoint may not be deployed"
222
+ )
223
+
224
+ execute_url = f"{self.server.endpoint_url}/execute"
225
+
226
+ try:
227
+ async with get_authenticated_httpx_client(timeout=self.timeout) as client:
228
+ response = await client.post(execute_url, json=request)
229
+ response.raise_for_status()
230
+ return response.json()
231
+ except httpx.TimeoutException as e:
232
+ raise TimeoutError(
233
+ f"Execution timeout on {self.server.name} after {self.timeout}s: {e}"
234
+ ) from e
235
+ except httpx.HTTPStatusError as e:
236
+ # Truncate response body to prevent huge error messages
237
+ response_text = e.response.text
238
+ if len(response_text) > 500:
239
+ response_text = response_text[:500] + "... (truncated)"
240
+ raise RuntimeError(
241
+ f"HTTP error from endpoint {self.server.name}: "
242
+ f"{e.response.status_code} - {response_text}"
243
+ ) from e
244
+ except httpx.RequestError as e:
245
+ raise ConnectionError(
246
+ f"Failed to connect to endpoint {self.server.name} ({execute_url}): {e}"
247
+ ) from e
248
+
249
+ async def _execute_via_user_route(
250
+ self,
251
+ func: Callable[..., Any],
252
+ method: str,
253
+ path: str,
254
+ *args: Any,
255
+ **kwargs: Any,
256
+ ) -> Any:
257
+ """Execute function by calling user-defined HTTP route.
258
+
259
+ Maps function arguments to JSON request body and makes HTTP request
260
+ to the user-defined route. The response is parsed as JSON and returned directly.
261
+
262
+ Args:
263
+ func: Function being called (used for signature inspection)
264
+ method: HTTP method (GET, POST, PUT, DELETE, PATCH)
265
+ path: URL path (e.g., /api/process)
266
+ *args: Function positional arguments
267
+ **kwargs: Function keyword arguments
268
+
269
+ Returns:
270
+ Function result (parsed from JSON response)
271
+
272
+ Raises:
273
+ ValueError: If endpoint_url not available
274
+ TimeoutError: If request times out
275
+ RuntimeError: If HTTP error occurs
276
+ ConnectionError: If connection fails
277
+ """
278
+ if not self.server.endpoint_url:
279
+ raise ValueError(
280
+ "Endpoint URL not available - endpoint may not be deployed"
281
+ )
282
+
283
+ # Get function signature to map args to parameter names
284
+ sig = inspect.signature(func)
285
+ params = list(sig.parameters.keys())
286
+
287
+ # Map positional args to parameter names
288
+ body = {}
289
+ for i, arg in enumerate(args):
290
+ if i < len(params):
291
+ body[params[i]] = arg
292
+ body.update(kwargs)
293
+
294
+ # Construct full URL
295
+ url = f"{self.server.endpoint_url}{path}"
296
+ log.debug(f"Executing via user route: {method} {url}")
297
+
298
+ try:
299
+ async with get_authenticated_httpx_client(timeout=self.timeout) as client:
300
+ response = await client.request(method, url, json=body)
301
+ response.raise_for_status()
302
+ result = response.json()
303
+ log.debug(
304
+ f"User route execution successful (type={type(result).__name__})"
305
+ )
306
+ return result
307
+ except httpx.TimeoutException as e:
308
+ raise TimeoutError(
309
+ f"Execution timeout on {self.server.name} after {self.timeout}s: {e}"
310
+ ) from e
311
+ except httpx.HTTPStatusError as e:
312
+ # Truncate response body to prevent huge error messages
313
+ response_text = e.response.text
314
+ if len(response_text) > 500:
315
+ response_text = response_text[:500] + "... (truncated)"
316
+ raise RuntimeError(
317
+ f"HTTP error from endpoint {self.server.name}: "
318
+ f"{e.response.status_code} - {response_text}"
319
+ ) from e
320
+ except httpx.RequestError as e:
321
+ raise ConnectionError(
322
+ f"Failed to connect to endpoint {self.server.name} ({url}): {e}"
323
+ ) from e
324
+
325
+ def _handle_response(self, response: Dict[str, Any]) -> Any:
326
+ """Deserialize and validate response.
327
+
328
+ Args:
329
+ response: Response dictionary from endpoint
330
+
331
+ Returns:
332
+ Deserialized function result
333
+
334
+ Raises:
335
+ ValueError: If response format is invalid
336
+ Exception: If response indicates error
337
+ """
338
+ if not isinstance(response, dict):
339
+ raise ValueError(f"Invalid response type: {type(response)}")
340
+
341
+ if response.get("success"):
342
+ result_b64 = response.get("result")
343
+ if result_b64 is None:
344
+ raise ValueError("Response marked success but result is None")
345
+
346
+ try:
347
+ result = deserialize_arg(result_b64)
348
+ log.debug(
349
+ f"Successfully deserialized response result (type={type(result).__name__})"
350
+ )
351
+ return result
352
+ except Exception as e:
353
+ raise ValueError(f"Failed to deserialize result: {e}") from e
354
+ else:
355
+ error = response.get("error", "Unknown error")
356
+ log.warning(f"Remote execution failed: {error}")
357
+ raise Exception(f"Remote execution failed: {error}")
@@ -1,13 +1,18 @@
1
1
  import logging
2
+ import os
2
3
  from functools import singledispatch
3
- from .live_serverless import LiveServerlessStub
4
- from .serverless import ServerlessEndpointStub
4
+
5
5
  from ..core.resources import (
6
+ CpuLiveServerless,
6
7
  CpuServerlessEndpoint,
8
+ LiveLoadBalancer,
7
9
  LiveServerless,
10
+ LoadBalancerSlsResource,
8
11
  ServerlessEndpoint,
9
12
  )
10
-
13
+ from .live_serverless import LiveServerlessStub
14
+ from .load_balancer_sls import LoadBalancerSlsStub
15
+ from .serverless import ServerlessEndpointStub
11
16
 
12
17
  log = logging.getLogger(__name__)
13
18
 
@@ -20,34 +25,104 @@ def stub_resource(resource, **extra):
20
25
  return fallback
21
26
 
22
27
 
23
- @stub_resource.register(LiveServerless)
24
- def _(resource, **extra):
28
+ def _create_live_serverless_stub(resource, **extra):
29
+ """Create a live serverless stub for both LiveServerless and CpuLiveServerless."""
30
+ stub = LiveServerlessStub(resource)
31
+
32
+ # Function execution
25
33
  async def stubbed_resource(
26
- func, dependencies, system_dependencies, *args, **kwargs
34
+ func,
35
+ dependencies,
36
+ system_dependencies,
37
+ accelerate_downloads,
38
+ *args,
39
+ **kwargs,
27
40
  ) -> dict:
28
41
  if args == (None,):
29
- # cleanup: when the function is called with no args
30
42
  args = []
31
43
 
32
- stub = LiveServerlessStub(resource)
33
44
  request = stub.prepare_request(
34
- func, dependencies, system_dependencies, *args, **kwargs
45
+ func,
46
+ dependencies,
47
+ system_dependencies,
48
+ accelerate_downloads,
49
+ *args,
50
+ **kwargs,
35
51
  )
36
52
  response = await stub.ExecuteFunction(request)
37
53
  return stub.handle_response(response)
38
54
 
55
+ # Class method execution
56
+ async def execute_class_method(request):
57
+ response = await stub.ExecuteFunction(request)
58
+ return stub.handle_response(response)
59
+
60
+ # Inject ProductionWrapper if in production mode
61
+ if os.getenv("RUNPOD_ENDPOINT_ID"):
62
+ try:
63
+ from ..runtime.production_wrapper import create_production_wrapper
64
+
65
+ wrapper = create_production_wrapper()
66
+ original_stubbed = stubbed_resource
67
+ original_class_method = execute_class_method
68
+
69
+ async def wrapped_stubbed(
70
+ func,
71
+ dependencies,
72
+ system_dependencies,
73
+ accelerate_downloads,
74
+ *args,
75
+ **kwargs,
76
+ ):
77
+ return await wrapper.wrap_function_execution(
78
+ original_stubbed,
79
+ func,
80
+ dependencies,
81
+ system_dependencies,
82
+ accelerate_downloads,
83
+ *args,
84
+ **kwargs,
85
+ )
86
+
87
+ async def wrapped_class_method(request):
88
+ return await wrapper.wrap_class_method_execution(
89
+ original_class_method, request
90
+ )
91
+
92
+ stubbed_resource = wrapped_stubbed
93
+ execute_class_method = wrapped_class_method
94
+
95
+ except ImportError:
96
+ log.warning(
97
+ "ProductionWrapper not available, cross-endpoint routing disabled"
98
+ )
99
+
100
+ # Attach the method to the function
101
+ stubbed_resource.execute_class_method = execute_class_method
102
+
39
103
  return stubbed_resource
40
104
 
41
105
 
106
+ @stub_resource.register(LiveServerless)
107
+ def _(resource, **extra):
108
+ return _create_live_serverless_stub(resource, **extra)
109
+
110
+
111
+ @stub_resource.register(CpuLiveServerless)
112
+ def _(resource, **extra):
113
+ return _create_live_serverless_stub(resource, **extra)
114
+
115
+
42
116
  @stub_resource.register(ServerlessEndpoint)
43
117
  def _(resource, **extra):
44
118
  async def stubbed_resource(
45
- func, dependencies, system_dependencies, *args, **kwargs
119
+ func,
120
+ dependencies,
121
+ system_dependencies,
122
+ accelerate_downloads,
123
+ *args,
124
+ **kwargs,
46
125
  ) -> dict:
47
- if args == (None,):
48
- # cleanup: when the function is called with no args
49
- args = []
50
-
51
126
  if dependencies or system_dependencies:
52
127
  log.warning(
53
128
  "Dependencies are not supported for ServerlessEndpoint. "
@@ -65,12 +140,13 @@ def _(resource, **extra):
65
140
  @stub_resource.register(CpuServerlessEndpoint)
66
141
  def _(resource, **extra):
67
142
  async def stubbed_resource(
68
- func, dependencies, system_dependencies, *args, **kwargs
143
+ func,
144
+ dependencies,
145
+ system_dependencies,
146
+ accelerate_downloads,
147
+ *args,
148
+ **kwargs,
69
149
  ) -> dict:
70
- if args == (None,):
71
- # cleanup: when the function is called with no args
72
- args = []
73
-
74
150
  if dependencies or system_dependencies:
75
151
  log.warning(
76
152
  "Dependencies are not supported for CpuServerlessEndpoint. "
@@ -83,3 +159,53 @@ def _(resource, **extra):
83
159
  return stub.handle_response(response)
84
160
 
85
161
  return stubbed_resource
162
+
163
+
164
+ @stub_resource.register(LoadBalancerSlsResource)
165
+ def _(resource, **extra):
166
+ """Create stub for LoadBalancerSlsResource (HTTP-based execution)."""
167
+ stub = LoadBalancerSlsStub(resource)
168
+
169
+ async def stubbed_resource(
170
+ func,
171
+ dependencies,
172
+ system_dependencies,
173
+ accelerate_downloads,
174
+ *args,
175
+ **kwargs,
176
+ ) -> dict:
177
+ return await stub(
178
+ func,
179
+ dependencies,
180
+ system_dependencies,
181
+ accelerate_downloads,
182
+ *args,
183
+ **kwargs,
184
+ )
185
+
186
+ return stubbed_resource
187
+
188
+
189
+ @stub_resource.register(LiveLoadBalancer)
190
+ def _(resource, **extra):
191
+ """Create stub for LiveLoadBalancer (HTTP-based execution, local testing)."""
192
+ stub = LoadBalancerSlsStub(resource)
193
+
194
+ async def stubbed_resource(
195
+ func,
196
+ dependencies,
197
+ system_dependencies,
198
+ accelerate_downloads,
199
+ *args,
200
+ **kwargs,
201
+ ) -> dict:
202
+ return await stub(
203
+ func,
204
+ dependencies,
205
+ system_dependencies,
206
+ accelerate_downloads,
207
+ *args,
208
+ **kwargs,
209
+ )
210
+
211
+ return stubbed_resource