tetra-rp 0.6.0__py3-none-any.whl → 0.7.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.
- tetra_rp/client.py +2 -12
- tetra_rp/core/api/runpod.py +9 -27
- tetra_rp/core/resources/network_volume.py +20 -11
- tetra_rp/core/resources/serverless.py +42 -10
- {tetra_rp-0.6.0.dist-info → tetra_rp-0.7.0.dist-info}/METADATA +1 -1
- {tetra_rp-0.6.0.dist-info → tetra_rp-0.7.0.dist-info}/RECORD +8 -8
- {tetra_rp-0.6.0.dist-info → tetra_rp-0.7.0.dist-info}/WHEEL +0 -0
- {tetra_rp-0.6.0.dist-info → tetra_rp-0.7.0.dist-info}/top_level.txt +0 -0
tetra_rp/client.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from functools import wraps
|
|
3
|
-
from typing import List
|
|
4
|
-
from .core.resources import ServerlessResource, ResourceManager
|
|
3
|
+
from typing import List
|
|
4
|
+
from .core.resources import ServerlessResource, ResourceManager
|
|
5
5
|
from .stubs import stub_resource
|
|
6
6
|
|
|
7
7
|
|
|
@@ -12,7 +12,6 @@ def remote(
|
|
|
12
12
|
resource_config: ServerlessResource,
|
|
13
13
|
dependencies: List[str] = None,
|
|
14
14
|
system_dependencies: List[str] = None,
|
|
15
|
-
mount_volume: Optional[NetworkVolume] = None,
|
|
16
15
|
**extra,
|
|
17
16
|
):
|
|
18
17
|
"""
|
|
@@ -49,15 +48,6 @@ def remote(
|
|
|
49
48
|
def decorator(func):
|
|
50
49
|
@wraps(func)
|
|
51
50
|
async def wrapper(*args, **kwargs):
|
|
52
|
-
# Create netowrk volume if mount_volume is provided
|
|
53
|
-
if mount_volume:
|
|
54
|
-
try:
|
|
55
|
-
network_volume = await mount_volume.deploy()
|
|
56
|
-
resource_config.networkVolumeId = network_volume.id
|
|
57
|
-
except Exception as e:
|
|
58
|
-
log.error(f"Failed to create or mount network volume: {e}")
|
|
59
|
-
raise
|
|
60
|
-
|
|
61
51
|
resource_manager = ResourceManager()
|
|
62
52
|
remote_resource = await resource_manager.get_or_deploy_resource(
|
|
63
53
|
resource_config
|
tetra_rp/core/api/runpod.py
CHANGED
|
@@ -3,11 +3,12 @@ Direct GraphQL communication with Runpod API.
|
|
|
3
3
|
Bypasses the outdated runpod-python SDK limitations.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
import os
|
|
7
6
|
import json
|
|
8
|
-
import aiohttp
|
|
9
|
-
from typing import Dict, Any, Optional
|
|
10
7
|
import logging
|
|
8
|
+
import os
|
|
9
|
+
from typing import Any, Dict, Optional
|
|
10
|
+
|
|
11
|
+
import aiohttp
|
|
11
12
|
|
|
12
13
|
log = logging.getLogger(__name__)
|
|
13
14
|
|
|
@@ -267,31 +268,12 @@ class RunpodRestClient:
|
|
|
267
268
|
raise Exception(f"HTTP request failed: {e}")
|
|
268
269
|
|
|
269
270
|
async def create_network_volume(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
270
|
-
"""
|
|
271
|
-
|
|
271
|
+
"""Create a network volume in Runpod."""
|
|
272
|
+
log.debug(f"Creating network volume: {payload.get('name', 'unnamed')}")
|
|
272
273
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
size_gb (int): The size of the volume in GB.
|
|
277
|
-
|
|
278
|
-
Returns:
|
|
279
|
-
Dict[str, Any]: The created network volume details.
|
|
280
|
-
"""
|
|
281
|
-
datacenter_id = payload.get("dataCenterId")
|
|
282
|
-
if hasattr(datacenter_id, "value"):
|
|
283
|
-
# If datacenter_id is an enum, get its value
|
|
284
|
-
datacenter_id = datacenter_id.value
|
|
285
|
-
data = {
|
|
286
|
-
"dataCenterId": datacenter_id,
|
|
287
|
-
"name": payload.get("name"),
|
|
288
|
-
"size": payload.get("size"),
|
|
289
|
-
}
|
|
290
|
-
url = f"{RUNPOD_REST_API_URL}/networkvolumes"
|
|
291
|
-
|
|
292
|
-
log.debug(f"Creating network volume: {data.get('name', 'unnamed')}")
|
|
293
|
-
|
|
294
|
-
result = await self._execute_rest("POST", url, data)
|
|
274
|
+
result = await self._execute_rest(
|
|
275
|
+
"POST", f"{RUNPOD_REST_API_URL}/networkvolumes", payload
|
|
276
|
+
)
|
|
295
277
|
|
|
296
278
|
log.info(
|
|
297
279
|
f"Created network volume: {result.get('id', 'unknown')} - {result.get('name', 'unnamed')}"
|
|
@@ -4,6 +4,7 @@ from typing import Optional
|
|
|
4
4
|
|
|
5
5
|
from pydantic import (
|
|
6
6
|
Field,
|
|
7
|
+
field_serializer,
|
|
7
8
|
)
|
|
8
9
|
|
|
9
10
|
from ..api.runpod import RunpodRestClient
|
|
@@ -20,8 +21,6 @@ class DataCenter(str, Enum):
|
|
|
20
21
|
"""
|
|
21
22
|
|
|
22
23
|
EU_RO_1 = "EU-RO-1"
|
|
23
|
-
US_WA_1 = "US-WA-1"
|
|
24
|
-
US_CA_1 = "US-CA-1"
|
|
25
24
|
|
|
26
25
|
|
|
27
26
|
class NetworkVolume(DeployableResource):
|
|
@@ -33,10 +32,20 @@ class NetworkVolume(DeployableResource):
|
|
|
33
32
|
|
|
34
33
|
"""
|
|
35
34
|
|
|
36
|
-
|
|
35
|
+
# Internal fixed value
|
|
36
|
+
dataCenterId: DataCenter = Field(default=DataCenter.EU_RO_1, frozen=True)
|
|
37
|
+
|
|
37
38
|
id: Optional[str] = Field(default=None)
|
|
38
39
|
name: Optional[str] = None
|
|
39
|
-
size: Optional[int] =
|
|
40
|
+
size: Optional[int] = Field(default=10, gt=0) # Size in GB
|
|
41
|
+
|
|
42
|
+
def __str__(self) -> str:
|
|
43
|
+
return f"{self.__class__.__name__}:{self.id}"
|
|
44
|
+
|
|
45
|
+
@field_serializer("dataCenterId")
|
|
46
|
+
def serialize_data_center_id(self, value: Optional[DataCenter]) -> Optional[str]:
|
|
47
|
+
"""Convert DataCenter enum to string."""
|
|
48
|
+
return value.value if value is not None else None
|
|
40
49
|
|
|
41
50
|
@property
|
|
42
51
|
def is_created(self) -> bool:
|
|
@@ -79,17 +88,17 @@ class NetworkVolume(DeployableResource):
|
|
|
79
88
|
try:
|
|
80
89
|
# If the resource is already deployed, return it
|
|
81
90
|
if self.is_deployed():
|
|
82
|
-
log.debug(
|
|
83
|
-
f"Network volume {self.id} is already deployed. Mounting existing volume."
|
|
84
|
-
)
|
|
85
|
-
log.info(f"Mounted existing network volume: {self.id}")
|
|
91
|
+
log.debug(f"{self} exists")
|
|
86
92
|
return self
|
|
87
93
|
|
|
88
94
|
# Create the network volume
|
|
89
|
-
|
|
95
|
+
async with RunpodRestClient() as client:
|
|
96
|
+
# Create the network volume
|
|
97
|
+
payload = self.model_dump(exclude_none=True)
|
|
98
|
+
result = await client.create_network_volume(payload)
|
|
90
99
|
|
|
91
|
-
if self.
|
|
92
|
-
return
|
|
100
|
+
if volume := self.__class__(**result):
|
|
101
|
+
return volume
|
|
93
102
|
|
|
94
103
|
raise ValueError("Deployment failed, no volume was created.")
|
|
95
104
|
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import logging
|
|
3
|
-
from typing import Any, Dict, List, Optional
|
|
4
3
|
from enum import Enum
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
|
|
5
6
|
from pydantic import (
|
|
7
|
+
BaseModel,
|
|
8
|
+
Field,
|
|
6
9
|
field_serializer,
|
|
7
10
|
field_validator,
|
|
8
11
|
model_validator,
|
|
9
|
-
BaseModel,
|
|
10
|
-
Field,
|
|
11
12
|
)
|
|
12
|
-
|
|
13
13
|
from runpod.endpoint.runner import Job
|
|
14
14
|
|
|
15
15
|
from ..api.runpod import RunpodGraphQLClient
|
|
16
16
|
from ..utils.backoff import get_backoff_delay
|
|
17
|
-
|
|
18
|
-
from .cloud import runpod
|
|
19
17
|
from .base import DeployableResource
|
|
20
|
-
from .
|
|
21
|
-
from .
|
|
18
|
+
from .cloud import runpod
|
|
19
|
+
from .constants import CONSOLE_URL
|
|
22
20
|
from .cpu import CpuInstanceType
|
|
23
21
|
from .environment import EnvironmentVars
|
|
24
|
-
from .
|
|
22
|
+
from .gpu import GpuGroup
|
|
23
|
+
from .network_volume import NetworkVolume
|
|
24
|
+
from .template import KeyValuePair, PodTemplate
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
# Environment variables are loaded from the .env file
|
|
@@ -62,7 +62,15 @@ class ServerlessResource(DeployableResource):
|
|
|
62
62
|
Base class for GPU serverless resource
|
|
63
63
|
"""
|
|
64
64
|
|
|
65
|
-
_input_only = {
|
|
65
|
+
_input_only = {
|
|
66
|
+
"id",
|
|
67
|
+
"cudaVersions",
|
|
68
|
+
"env",
|
|
69
|
+
"gpus",
|
|
70
|
+
"flashboot",
|
|
71
|
+
"imageName",
|
|
72
|
+
"networkVolume",
|
|
73
|
+
}
|
|
66
74
|
|
|
67
75
|
# === Input-only Fields ===
|
|
68
76
|
cudaVersions: Optional[List[CudaVersion]] = [] # for allowedCudaVersions
|
|
@@ -71,6 +79,8 @@ class ServerlessResource(DeployableResource):
|
|
|
71
79
|
gpus: Optional[List[GpuGroup]] = [GpuGroup.ANY] # for gpuIds
|
|
72
80
|
imageName: Optional[str] = "" # for template.imageName
|
|
73
81
|
|
|
82
|
+
networkVolume: Optional[NetworkVolume] = None
|
|
83
|
+
|
|
74
84
|
# === Input Fields ===
|
|
75
85
|
executionTimeoutMs: Optional[int] = None
|
|
76
86
|
gpuCount: Optional[int] = 1
|
|
@@ -142,6 +152,10 @@ class ServerlessResource(DeployableResource):
|
|
|
142
152
|
if self.flashboot:
|
|
143
153
|
self.name += "-fb"
|
|
144
154
|
|
|
155
|
+
if self.networkVolume and self.networkVolume.is_created:
|
|
156
|
+
# Volume already exists, use its ID
|
|
157
|
+
self.networkVolumeId = self.networkVolume.id
|
|
158
|
+
|
|
145
159
|
if self.instanceIds:
|
|
146
160
|
return self._sync_input_fields_cpu()
|
|
147
161
|
else:
|
|
@@ -177,6 +191,21 @@ class ServerlessResource(DeployableResource):
|
|
|
177
191
|
|
|
178
192
|
return self
|
|
179
193
|
|
|
194
|
+
async def _ensure_network_volume_deployed(self) -> None:
|
|
195
|
+
"""
|
|
196
|
+
Ensures network volume is deployed and ready.
|
|
197
|
+
Updates networkVolumeId with the deployed volume ID.
|
|
198
|
+
"""
|
|
199
|
+
if self.networkVolumeId:
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
if not self.networkVolume:
|
|
203
|
+
log.info(f"{self.name} requires a default network volume")
|
|
204
|
+
self.networkVolume = NetworkVolume(name=f"{self.name}-volume")
|
|
205
|
+
|
|
206
|
+
if deployedNetworkVolume := await self.networkVolume.deploy():
|
|
207
|
+
self.networkVolumeId = deployedNetworkVolume.id
|
|
208
|
+
|
|
180
209
|
def is_deployed(self) -> bool:
|
|
181
210
|
"""
|
|
182
211
|
Checks if the serverless resource is deployed and available.
|
|
@@ -202,6 +231,9 @@ class ServerlessResource(DeployableResource):
|
|
|
202
231
|
log.debug(f"{self} exists")
|
|
203
232
|
return self
|
|
204
233
|
|
|
234
|
+
# NEW: Ensure network volume is deployed first
|
|
235
|
+
await self._ensure_network_volume_deployed()
|
|
236
|
+
|
|
205
237
|
async with RunpodGraphQLClient() as client:
|
|
206
238
|
payload = self.model_dump(exclude=self._input_only, exclude_none=True)
|
|
207
239
|
result = await client.create_endpoint(payload)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tetra_rp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: A Python library for distributed inference and serving of machine learning models
|
|
5
5
|
Author-email: Marut Pandya <pandyamarut@gmail.com>, Patrick Rachford <prachford@icloud.com>, Dean Quinanola <dean.quinanola@runpod.io>
|
|
6
6
|
License: MIT
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
tetra_rp/__init__.py,sha256=-1S5sYIKtnUV8V1HlSIbX1yZwiUrsO8J5b3ZEIR_phU,687
|
|
2
|
-
tetra_rp/client.py,sha256=
|
|
2
|
+
tetra_rp/client.py,sha256=5zerW5tTUnTSe75cRGgTBhqsKNXoCVWgb3Kzh9tJvPA,2209
|
|
3
3
|
tetra_rp/logger.py,sha256=gk5-PWp3k_GQ5DxndsRkBCX0jarp_3lgZ1oiTFuThQg,1125
|
|
4
4
|
tetra_rp/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
tetra_rp/core/api/__init__.py,sha256=oldrEKMwxYoBPLvPfVlaFS3wfUtTTxCN6-HzlpTh6vE,124
|
|
6
|
-
tetra_rp/core/api/runpod.py,sha256=
|
|
6
|
+
tetra_rp/core/api/runpod.py,sha256=sux4q6xg2PDRKJI5kLkcW4i8UISZUOmQxsdf0g6wgpw,9711
|
|
7
7
|
tetra_rp/core/pool/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
tetra_rp/core/pool/cluster_manager.py,sha256=KJxEp_044HjnbOhfIdiXZbks_bFDYE1KgKeR5W9VvbY,6007
|
|
9
9
|
tetra_rp/core/pool/dataclass.py,sha256=YngS328_NTewY8Etitj4k7MmdM5GWqqE_OMbytrVNlw,338
|
|
@@ -18,9 +18,9 @@ tetra_rp/core/resources/cpu.py,sha256=YIE-tKolSU3JJzpPB7ey-PbRdqKWsJZ_Ad4h2OYaai
|
|
|
18
18
|
tetra_rp/core/resources/environment.py,sha256=FC9kJCa8YLSar75AKUKqJYnNLrUdjZj8ZTOrspBrS00,1267
|
|
19
19
|
tetra_rp/core/resources/gpu.py,sha256=2jIIMr8PNnlIAP8ZTKO8Imx-rdxXp2rbdSHJeVfjawk,1858
|
|
20
20
|
tetra_rp/core/resources/live_serverless.py,sha256=6r4I4TEx9AmZ0-OJvE86qrY0S7BEx9t_P2zwHVdtbew,1074
|
|
21
|
-
tetra_rp/core/resources/network_volume.py,sha256=
|
|
21
|
+
tetra_rp/core/resources/network_volume.py,sha256=5_gwJlxt77VHs7T0d41l3IMZR0LhdoyQhroXCYfFF7w,3274
|
|
22
22
|
tetra_rp/core/resources/resource_manager.py,sha256=kUVZDblfUzaG78S8FwOzu4rN6QSegUgQNK3fJ_X7l0w,2834
|
|
23
|
-
tetra_rp/core/resources/serverless.py,sha256=
|
|
23
|
+
tetra_rp/core/resources/serverless.py,sha256=RYH-gl_edEguGOlxR669Hfi_rXII4OEaYzlB2PhzOhI,15753
|
|
24
24
|
tetra_rp/core/resources/template.py,sha256=UkflJXZFWIbQkLuUt4oRLAjn-yIpw9_mT2X1cAH69CU,3141
|
|
25
25
|
tetra_rp/core/resources/utils.py,sha256=mgXfgz_NuHN_IC7TzMNdH9II-LMjxcDCG7syDTcPiGs,1721
|
|
26
26
|
tetra_rp/core/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -33,7 +33,7 @@ tetra_rp/stubs/__init__.py,sha256=ozKsHs8q0T7o2qhQEquub9hqomh1Htys53mMraaRu2E,72
|
|
|
33
33
|
tetra_rp/stubs/live_serverless.py,sha256=o1NH5XEwUD-27NXJsEGO0IwnuDp8iXwUiw5nZtaZZOI,4199
|
|
34
34
|
tetra_rp/stubs/registry.py,sha256=V4m3CeXl8j1pguHuuflxqpWeBgVDQ93YkhxJbElyP7Q,2599
|
|
35
35
|
tetra_rp/stubs/serverless.py,sha256=BM_a5Ml5VADBYu2WRNmo9qnicP8NnXDGl5ywifulbD0,947
|
|
36
|
-
tetra_rp-0.
|
|
37
|
-
tetra_rp-0.
|
|
38
|
-
tetra_rp-0.
|
|
39
|
-
tetra_rp-0.
|
|
36
|
+
tetra_rp-0.7.0.dist-info/METADATA,sha256=jcXAGoiAFJVTYosJV2SGMw8L14UkUw2PHNKKH5mXkR8,28055
|
|
37
|
+
tetra_rp-0.7.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
38
|
+
tetra_rp-0.7.0.dist-info/top_level.txt,sha256=bBay7JTDwJXsTYvVjrwno9hnF-j0q272lk65f2AcPjU,9
|
|
39
|
+
tetra_rp-0.7.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|