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 CHANGED
@@ -1,7 +1,7 @@
1
1
  import logging
2
2
  from functools import wraps
3
- from typing import List, Optional
4
- from .core.resources import ServerlessResource, ResourceManager, NetworkVolume
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
@@ -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
- Create a network volume in Runpod.
271
+ """Create a network volume in Runpod."""
272
+ log.debug(f"Creating network volume: {payload.get('name', 'unnamed')}")
272
273
 
273
- Args:
274
- datacenter_id (str): The ID of the datacenter where the volume will be created.
275
- name (str): The name of the network volume.
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
- dataCenterId: Optional[DataCenter] = None
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] = None # Size in GB
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
- self = await self.create_network_volume()
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.is_deployed():
92
- return self
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 .template import PodTemplate, KeyValuePair
21
- from .gpu import GpuGroup
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 .constants import CONSOLE_URL
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 = {"id", "cudaVersions", "env", "gpus", "flashboot", "imageName"}
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.6.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=sJk3kneFJo40OAtbxucbaJ6NxMs-kD2jkpXYwG0vJhM,2681
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=n1pfvzDceU_yO9iOn4B9Kst6YL2KD0m-migm3bvnfJM,10374
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=mzYA7Bf4JTFiDyH0sLrUbPjq-26MWgbbRWAMbX2h6iA,2881
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=lZMITI1cwOBVNX3dbFyR01AeRZ0eatNC4CRE7vwUrqs,14726
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.6.0.dist-info/METADATA,sha256=CPOdAGUy5vZOSLazBQubYChiQkuwDldW3G0K9KsyBlw,28055
37
- tetra_rp-0.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
- tetra_rp-0.6.0.dist-info/top_level.txt,sha256=bBay7JTDwJXsTYvVjrwno9hnF-j0q272lk65f2AcPjU,9
39
- tetra_rp-0.6.0.dist-info/RECORD,,
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,,