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,325 @@
1
+ """Metrics collection via structured logging for observability."""
2
+
3
+ import logging
4
+ from dataclasses import asdict, dataclass
5
+ from enum import Enum
6
+ from typing import Any, Dict, Optional
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class MetricType(Enum):
12
+ """Types of metrics that can be collected."""
13
+
14
+ COUNTER = "counter"
15
+ GAUGE = "gauge"
16
+ HISTOGRAM = "histogram"
17
+
18
+
19
+ @dataclass
20
+ class Metric:
21
+ """Representation of a single metric."""
22
+
23
+ metric_type: MetricType
24
+ metric_name: str
25
+ value: float
26
+ labels: Dict[str, Any]
27
+
28
+ def to_dict(self) -> Dict[str, Any]:
29
+ """Convert metric to dictionary.
30
+
31
+ Returns:
32
+ Dictionary representation of metric
33
+ """
34
+ return asdict(self)
35
+
36
+
37
+ class MetricsCollector:
38
+ """Collect metrics via structured logging."""
39
+
40
+ def __init__(self, namespace: str = "tetra.metrics", enabled: bool = True):
41
+ """Initialize metrics collector.
42
+
43
+ Args:
44
+ namespace: Namespace for metrics (used in structured logging)
45
+ enabled: Whether metrics collection is enabled
46
+ """
47
+ self.namespace = namespace
48
+ self.enabled = enabled
49
+
50
+ def counter(
51
+ self,
52
+ name: str,
53
+ value: float = 1.0,
54
+ labels: Optional[Dict[str, Any]] = None,
55
+ ) -> None:
56
+ """Record a counter metric (cumulative).
57
+
58
+ Args:
59
+ name: Name of the metric
60
+ value: Value to add to counter (default: 1.0)
61
+ labels: Optional labels/tags for the metric
62
+ """
63
+ if not self.enabled:
64
+ return
65
+
66
+ metric = Metric(MetricType.COUNTER, name, value, labels or {})
67
+ self._emit(metric)
68
+
69
+ def gauge(
70
+ self,
71
+ name: str,
72
+ value: float,
73
+ labels: Optional[Dict[str, Any]] = None,
74
+ ) -> None:
75
+ """Record a gauge metric (point-in-time value).
76
+
77
+ Args:
78
+ name: Name of the metric
79
+ value: Current value of the gauge
80
+ labels: Optional labels/tags for the metric
81
+ """
82
+ if not self.enabled:
83
+ return
84
+
85
+ metric = Metric(MetricType.GAUGE, name, value, labels or {})
86
+ self._emit(metric)
87
+
88
+ def histogram(
89
+ self,
90
+ name: str,
91
+ value: float,
92
+ labels: Optional[Dict[str, Any]] = None,
93
+ ) -> None:
94
+ """Record a histogram metric (distribution).
95
+
96
+ Args:
97
+ name: Name of the metric
98
+ value: Value to add to histogram
99
+ labels: Optional labels/tags for the metric
100
+ """
101
+ if not self.enabled:
102
+ return
103
+
104
+ metric = Metric(MetricType.HISTOGRAM, name, value, labels or {})
105
+ self._emit(metric)
106
+
107
+ def _emit(self, metric: Metric) -> None:
108
+ """Emit metric via structured logging.
109
+
110
+ Args:
111
+ metric: Metric to emit
112
+ """
113
+ try:
114
+ logger.info(
115
+ f"[METRIC] {metric.metric_name}={metric.value}",
116
+ extra={
117
+ "namespace": self.namespace,
118
+ "metric": metric.to_dict(),
119
+ },
120
+ )
121
+ except Exception as e:
122
+ logger.error(f"Failed to emit metric {metric.metric_name}: {e}")
123
+
124
+
125
+ # Global metrics collector instance
126
+ _collector: Optional[MetricsCollector] = None
127
+
128
+
129
+ def get_metrics_collector(
130
+ namespace: str = "tetra.metrics", enabled: bool = True
131
+ ) -> MetricsCollector:
132
+ """Get global metrics collector (lazy-loaded).
133
+
134
+ Args:
135
+ namespace: Namespace for metrics
136
+ enabled: Whether metrics collection is enabled
137
+
138
+ Returns:
139
+ MetricsCollector instance
140
+ """
141
+ global _collector
142
+ if _collector is None:
143
+ _collector = MetricsCollector(namespace=namespace, enabled=enabled)
144
+ return _collector
145
+
146
+
147
+ def set_metrics_collector(collector: MetricsCollector) -> None:
148
+ """Set global metrics collector (for testing).
149
+
150
+ Args:
151
+ collector: MetricsCollector instance
152
+ """
153
+ global _collector
154
+ _collector = collector
155
+
156
+
157
+ class CircuitBreakerMetrics:
158
+ """Helper for emitting circuit breaker metrics."""
159
+
160
+ def __init__(self, collector: Optional[MetricsCollector] = None):
161
+ """Initialize circuit breaker metrics helper.
162
+
163
+ Args:
164
+ collector: Optional MetricsCollector instance (uses global if not provided)
165
+ """
166
+ self.collector = collector or get_metrics_collector()
167
+
168
+ def state_changed(
169
+ self, endpoint_url: str, new_state: str, previous_state: str
170
+ ) -> None:
171
+ """Emit metric when circuit breaker state changes.
172
+
173
+ Args:
174
+ endpoint_url: URL of the endpoint
175
+ new_state: New circuit state
176
+ previous_state: Previous circuit state
177
+ """
178
+ self.collector.counter(
179
+ "circuit_breaker_state_changes",
180
+ value=1.0,
181
+ labels={
182
+ "endpoint_url": endpoint_url,
183
+ "new_state": new_state,
184
+ "previous_state": previous_state,
185
+ },
186
+ )
187
+
188
+ def endpoint_requests(self, endpoint_url: str, status: str, count: int = 1) -> None:
189
+ """Emit metric for endpoint requests.
190
+
191
+ Args:
192
+ endpoint_url: URL of the endpoint
193
+ status: Request status (success, failure, etc.)
194
+ count: Number of requests
195
+ """
196
+ self.collector.counter(
197
+ "endpoint_requests",
198
+ value=float(count),
199
+ labels={"endpoint_url": endpoint_url, "status": status},
200
+ )
201
+
202
+ def endpoint_latency(self, endpoint_url: str, latency_ms: float) -> None:
203
+ """Emit metric for endpoint latency.
204
+
205
+ Args:
206
+ endpoint_url: URL of the endpoint
207
+ latency_ms: Latency in milliseconds
208
+ """
209
+ self.collector.histogram(
210
+ "endpoint_latency",
211
+ value=latency_ms,
212
+ labels={"endpoint_url": endpoint_url},
213
+ )
214
+
215
+ def in_flight_requests(self, endpoint_url: str, count: int) -> None:
216
+ """Emit metric for in-flight requests.
217
+
218
+ Args:
219
+ endpoint_url: URL of the endpoint
220
+ count: Current number of in-flight requests
221
+ """
222
+ self.collector.gauge(
223
+ "in_flight_requests",
224
+ value=float(count),
225
+ labels={"endpoint_url": endpoint_url},
226
+ )
227
+
228
+
229
+ class RetryMetrics:
230
+ """Helper for emitting retry metrics."""
231
+
232
+ def __init__(self, collector: Optional[MetricsCollector] = None):
233
+ """Initialize retry metrics helper.
234
+
235
+ Args:
236
+ collector: Optional MetricsCollector instance (uses global if not provided)
237
+ """
238
+ self.collector = collector or get_metrics_collector()
239
+
240
+ def retry_attempt(
241
+ self, function_name: str, attempt: int, error: Optional[str] = None
242
+ ) -> None:
243
+ """Emit metric for retry attempt.
244
+
245
+ Args:
246
+ function_name: Name of the function being retried
247
+ attempt: Attempt number
248
+ error: Optional error message
249
+ """
250
+ labels = {
251
+ "function_name": function_name,
252
+ "attempt": str(attempt),
253
+ }
254
+ if error:
255
+ labels["error"] = error
256
+
257
+ self.collector.counter(
258
+ "retry_attempts",
259
+ value=1.0,
260
+ labels=labels,
261
+ )
262
+
263
+ def retry_success(self, function_name: str, total_attempts: int) -> None:
264
+ """Emit metric for successful retry.
265
+
266
+ Args:
267
+ function_name: Name of the function
268
+ total_attempts: Total attempts made before success
269
+ """
270
+ self.collector.counter(
271
+ "retry_success",
272
+ value=1.0,
273
+ labels={
274
+ "function_name": function_name,
275
+ "attempts": str(total_attempts),
276
+ },
277
+ )
278
+
279
+ def retry_exhausted(self, function_name: str, max_attempts: int) -> None:
280
+ """Emit metric when max retries exceeded.
281
+
282
+ Args:
283
+ function_name: Name of the function
284
+ max_attempts: Maximum attempts configured
285
+ """
286
+ self.collector.counter(
287
+ "retry_exhausted",
288
+ value=1.0,
289
+ labels={
290
+ "function_name": function_name,
291
+ "max_attempts": str(max_attempts),
292
+ },
293
+ )
294
+
295
+
296
+ class LoadBalancerMetrics:
297
+ """Helper for emitting load balancer metrics."""
298
+
299
+ def __init__(self, collector: Optional[MetricsCollector] = None):
300
+ """Initialize load balancer metrics helper.
301
+
302
+ Args:
303
+ collector: Optional MetricsCollector instance (uses global if not provided)
304
+ """
305
+ self.collector = collector or get_metrics_collector()
306
+
307
+ def endpoint_selected(
308
+ self, strategy: str, endpoint_url: str, total_candidates: int
309
+ ) -> None:
310
+ """Emit metric when endpoint is selected.
311
+
312
+ Args:
313
+ strategy: Load balancing strategy used
314
+ endpoint_url: Selected endpoint URL
315
+ total_candidates: Total candidate endpoints
316
+ """
317
+ self.collector.counter(
318
+ "load_balancer_selection",
319
+ value=1.0,
320
+ labels={
321
+ "strategy": strategy,
322
+ "endpoint_url": endpoint_url,
323
+ "candidates": str(total_candidates),
324
+ },
325
+ )
@@ -0,0 +1,73 @@
1
+ """Type-safe models for manifest handling."""
2
+
3
+ from dataclasses import asdict, dataclass, field
4
+ from typing import Any, Dict, List, Optional
5
+
6
+
7
+ @dataclass
8
+ class FunctionMetadata:
9
+ """Function metadata in manifest."""
10
+
11
+ name: str
12
+ module: str
13
+ is_async: bool
14
+ is_class: bool = False
15
+ http_method: Optional[str] = None
16
+ http_path: Optional[str] = None
17
+
18
+
19
+ @dataclass
20
+ class ResourceConfig:
21
+ """Resource configuration in manifest."""
22
+
23
+ resource_type: str
24
+ handler_file: str
25
+ functions: List[FunctionMetadata] = field(default_factory=list)
26
+
27
+ @classmethod
28
+ def from_dict(cls, data: Dict[str, Any]) -> "ResourceConfig":
29
+ """Load ResourceConfig from dict."""
30
+ functions = [
31
+ FunctionMetadata(**func_data) for func_data in data.get("functions", [])
32
+ ]
33
+ return cls(
34
+ resource_type=data["resource_type"],
35
+ handler_file=data["handler_file"],
36
+ functions=functions,
37
+ )
38
+
39
+
40
+ @dataclass
41
+ class Manifest:
42
+ """Type-safe manifest structure."""
43
+
44
+ version: str
45
+ generated_at: str
46
+ project_name: str
47
+ function_registry: Dict[str, str]
48
+ resources: Dict[str, ResourceConfig]
49
+ routes: Optional[Dict[str, Dict[str, str]]] = None
50
+
51
+ @classmethod
52
+ def from_dict(cls, data: Dict[str, Any]) -> "Manifest":
53
+ """Load Manifest from JSON dict."""
54
+ resources = {}
55
+ for resource_name, resource_data in data.get("resources", {}).items():
56
+ resources[resource_name] = ResourceConfig.from_dict(resource_data)
57
+
58
+ return cls(
59
+ version=data.get("version", "1.0"),
60
+ generated_at=data.get("generated_at", ""),
61
+ project_name=data.get("project_name", ""),
62
+ function_registry=data.get("function_registry", {}),
63
+ resources=resources,
64
+ routes=data.get("routes"),
65
+ )
66
+
67
+ def to_dict(self) -> Dict[str, Any]:
68
+ """Convert to JSON-serializable dict."""
69
+ result = asdict(self)
70
+ # Remove None routes to keep JSON clean
71
+ if result.get("routes") is None:
72
+ result.pop("routes", None)
73
+ return result