tetra-rp 0.5.5__py3-none-any.whl → 0.6.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/__init__.py CHANGED
@@ -20,6 +20,7 @@ from .core.resources import ( # noqa: E402
20
20
  ResourceManager,
21
21
  ServerlessEndpoint,
22
22
  runpod,
23
+ NetworkVolume,
23
24
  )
24
25
 
25
26
 
@@ -34,4 +35,5 @@ __all__ = [
34
35
  "ResourceManager",
35
36
  "ServerlessEndpoint",
36
37
  "runpod",
38
+ "NetworkVolume",
37
39
  ]
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, Optional
4
+ from .core.resources import ServerlessResource, ResourceManager, NetworkVolume
5
5
  from .stubs import stub_resource
6
6
 
7
7
 
@@ -12,6 +12,7 @@ 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,
15
16
  **extra,
16
17
  ):
17
18
  """
@@ -24,6 +25,8 @@ def remote(
24
25
  to be provisioned or used.
25
26
  dependencies (List[str], optional): A list of pip package names to be installed in the remote
26
27
  environment before executing the function. Defaults to None.
28
+ mount_volume (NetworkVolume, optional): Configuration for creating and mounting a network volume.
29
+ Should contain 'size', 'datacenter_id', and 'name' keys. Defaults to None.
27
30
  extra (dict, optional): Additional parameters for the execution of the resource. Defaults to an empty dict.
28
31
 
29
32
  Returns:
@@ -46,6 +49,15 @@ def remote(
46
49
  def decorator(func):
47
50
  @wraps(func)
48
51
  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
+
49
61
  resource_manager = ResourceManager()
50
62
  remote_resource = await resource_manager.get_or_deploy_resource(
51
63
  resource_config
@@ -1,5 +1,6 @@
1
- from .runpod import RunpodGraphQLClient
1
+ from .runpod import RunpodGraphQLClient, RunpodRestClient
2
2
 
3
3
  __all__ = [
4
4
  "RunpodGraphQLClient",
5
+ "RunpodRestClient",
5
6
  ]
@@ -12,6 +12,7 @@ import logging
12
12
  log = logging.getLogger(__name__)
13
13
 
14
14
  RUNPOD_API_BASE_URL = os.environ.get("RUNPOD_API_BASE_URL", "https://api.runpod.io")
15
+ RUNPOD_REST_API_URL = os.environ.get("RUNPOD_REST_API_URL", "https://rest.runpod.io/v1")
15
16
 
16
17
 
17
18
  class RunpodGraphQLClient:
@@ -210,3 +211,101 @@ class RunpodGraphQLClient:
210
211
 
211
212
  async def __aexit__(self, exc_type, exc_val, exc_tb):
212
213
  await self.close()
214
+
215
+
216
+ class RunpodRestClient:
217
+ """
218
+ Runpod REST client for Runpod API.
219
+ Provides methods to interact with Runpod's REST endpoints.
220
+ """
221
+
222
+ def __init__(self, api_key: Optional[str] = None):
223
+ self.api_key = api_key or os.getenv("RUNPOD_API_KEY")
224
+ if not self.api_key:
225
+ raise ValueError("Runpod API key is required")
226
+
227
+ self.session: Optional[aiohttp.ClientSession] = None
228
+
229
+ async def _get_session(self) -> aiohttp.ClientSession:
230
+ """Get or create an aiohttp session."""
231
+ if self.session is None or self.session.closed:
232
+ timeout = aiohttp.ClientTimeout(total=300) # 5 minute timeout
233
+ self.session = aiohttp.ClientSession(
234
+ timeout=timeout,
235
+ headers={
236
+ "Authorization": f"Bearer {self.api_key}",
237
+ "Content-Type": "application/json",
238
+ },
239
+ )
240
+ return self.session
241
+
242
+ async def _execute_rest(
243
+ self, method: str, url: str, data: Optional[Dict[str, Any]] = None
244
+ ) -> Dict[str, Any]:
245
+ """Execute a REST API request."""
246
+ session = await self._get_session()
247
+
248
+ log.debug(f"REST Request: {method} {url}")
249
+ log.debug(f"REST Data: {json.dumps(data, indent=2) if data else 'None'}")
250
+
251
+ try:
252
+ async with session.request(method, url, json=data) as response:
253
+ response_data = await response.json()
254
+
255
+ log.debug(f"REST Response Status: {response.status}")
256
+ log.debug(f"REST Response: {json.dumps(response_data, indent=2)}")
257
+
258
+ if response.status >= 400:
259
+ raise Exception(
260
+ f"REST request failed: {response.status} - {response_data}"
261
+ )
262
+
263
+ return response_data
264
+
265
+ except aiohttp.ClientError as e:
266
+ log.error(f"HTTP client error: {e}")
267
+ raise Exception(f"HTTP request failed: {e}")
268
+
269
+ async def create_network_volume(self, payload: Dict[str, Any]) -> Dict[str, Any]:
270
+ """
271
+ Create a network volume in Runpod.
272
+
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)
295
+
296
+ log.info(
297
+ f"Created network volume: {result.get('id', 'unknown')} - {result.get('name', 'unnamed')}"
298
+ )
299
+
300
+ return result
301
+
302
+ async def close(self):
303
+ """Close the HTTP session."""
304
+ if self.session and not self.session.closed:
305
+ await self.session.close()
306
+
307
+ async def __aenter__(self):
308
+ return self
309
+
310
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
311
+ await self.close()
@@ -12,6 +12,7 @@ from .serverless import (
12
12
  CudaVersion,
13
13
  )
14
14
  from .template import PodTemplate
15
+ from .network_volume import NetworkVolume
15
16
 
16
17
 
17
18
  __all__ = [
@@ -30,4 +31,5 @@ __all__ = [
30
31
  "ServerlessResource",
31
32
  "ServerlessEndpoint",
32
33
  "PodTemplate",
34
+ "NetworkVolume",
33
35
  ]
@@ -0,0 +1,4 @@
1
+ import os
2
+
3
+ CONSOLE_BASE_URL = os.environ.get("CONSOLE_BASE_URL", "https://console.runpod.io")
4
+ CONSOLE_URL = f"{CONSOLE_BASE_URL}/serverless/user/endpoint/%s"
@@ -0,0 +1,98 @@
1
+ import logging
2
+ from enum import Enum
3
+ from typing import Optional
4
+
5
+ from pydantic import (
6
+ Field,
7
+ )
8
+
9
+ from ..api.runpod import RunpodRestClient
10
+ from .base import DeployableResource
11
+ from .constants import CONSOLE_BASE_URL
12
+
13
+ log = logging.getLogger(__name__)
14
+
15
+
16
+ class DataCenter(str, Enum):
17
+ """
18
+ Enum representing available data centers for network volumes.
19
+ #TODO: Add more data centers as needed. Lock this to the available data center.
20
+ """
21
+
22
+ EU_RO_1 = "EU-RO-1"
23
+ US_WA_1 = "US-WA-1"
24
+ US_CA_1 = "US-CA-1"
25
+
26
+
27
+ class NetworkVolume(DeployableResource):
28
+ """
29
+ NetworkVolume resource for creating and managing Runpod netowrk volumes.
30
+
31
+ This class handles the creation, deployment, and management of network volumes
32
+ that can be attached to serverless resources.
33
+
34
+ """
35
+
36
+ dataCenterId: Optional[DataCenter] = None
37
+ id: Optional[str] = Field(default=None)
38
+ name: Optional[str] = None
39
+ size: Optional[int] = None # Size in GB
40
+
41
+ @property
42
+ def is_created(self) -> bool:
43
+ "Returns True if the network volume already exists."
44
+ return self.id is not None
45
+
46
+ @property
47
+ def url(self) -> str:
48
+ """
49
+ Returns the URL for the network volume resource.
50
+ """
51
+ if not self.id:
52
+ raise ValueError("Network volume ID is not set")
53
+ return f"{CONSOLE_BASE_URL}/user/storage"
54
+
55
+ async def create_network_volume(self) -> str:
56
+ """
57
+ Creates a network volume using the provided configuration.
58
+ Returns the volume ID.
59
+ """
60
+ async with RunpodRestClient() as client:
61
+ # Create the network volume
62
+ payload = self.model_dump(exclude_none=True)
63
+ result = await client.create_network_volume(payload)
64
+
65
+ if volume := self.__class__(**result):
66
+ return volume
67
+
68
+ def is_deployed(self) -> bool:
69
+ """
70
+ Checks if the network volume resource is deployed and available.
71
+ """
72
+ return self.id is not None
73
+
74
+ async def deploy(self) -> "DeployableResource":
75
+ """
76
+ Deploys the network volume resource using the provided configuration.
77
+ Returns a DeployableResource object.
78
+ """
79
+ try:
80
+ # If the resource is already deployed, return it
81
+ 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}")
86
+ return self
87
+
88
+ # Create the network volume
89
+ self = await self.create_network_volume()
90
+
91
+ if self.is_deployed():
92
+ return self
93
+
94
+ raise ValueError("Deployment failed, no volume was created.")
95
+
96
+ except Exception as e:
97
+ log.error(f"{self} failed to deploy: {e}")
98
+ raise
@@ -1,6 +1,5 @@
1
1
  import asyncio
2
2
  import logging
3
- import os
4
3
  from typing import Any, Dict, List, Optional
5
4
  from enum import Enum
6
5
  from pydantic import (
@@ -22,6 +21,7 @@ from .template import PodTemplate, KeyValuePair
22
21
  from .gpu import GpuGroup
23
22
  from .cpu import CpuInstanceType
24
23
  from .environment import EnvironmentVars
24
+ from .constants import CONSOLE_URL
25
25
 
26
26
 
27
27
  # Environment variables are loaded from the .env file
@@ -39,10 +39,6 @@ def get_env_vars() -> Dict[str, str]:
39
39
  log = logging.getLogger(__name__)
40
40
 
41
41
 
42
- CONSOLE_BASE_URL = os.environ.get("CONSOLE_BASE_URL", "https://console.runpod.io")
43
- CONSOLE_URL = f"{CONSOLE_BASE_URL}/serverless/user/endpoint/%s"
44
-
45
-
46
42
  class ServerlessScalerType(Enum):
47
43
  QUEUE_DELAY = "QUEUE_DELAY"
48
44
  REQUEST_COUNT = "REQUEST_COUNT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tetra_rp
3
- Version: 0.5.5
3
+ Version: 0.6.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,24 +1,26 @@
1
- tetra_rp/__init__.py,sha256=_F4CMO3eS_GkPow864tZbBz81zYCfYQ-J3KLikHTli8,647
2
- tetra_rp/client.py,sha256=sE6GyGai9gpuBBQUzd2Lo7LKUTVbgY606KwlZgyhtGM,2016
1
+ tetra_rp/__init__.py,sha256=-1S5sYIKtnUV8V1HlSIbX1yZwiUrsO8J5b3ZEIR_phU,687
2
+ tetra_rp/client.py,sha256=sJk3kneFJo40OAtbxucbaJ6NxMs-kD2jkpXYwG0vJhM,2681
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
- tetra_rp/core/api/__init__.py,sha256=ErrKSdi9cn9BzT4cJ25X2fbS--GwI3Jwko5NgM-6Otc,82
6
- tetra_rp/core/api/runpod.py,sha256=v7Jo8vPjH1robl1-25V2PVDefCF9rpCKK758w5oUpG4,6869
5
+ tetra_rp/core/api/__init__.py,sha256=oldrEKMwxYoBPLvPfVlaFS3wfUtTTxCN6-HzlpTh6vE,124
6
+ tetra_rp/core/api/runpod.py,sha256=n1pfvzDceU_yO9iOn4B9Kst6YL2KD0m-migm3bvnfJM,10374
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
10
10
  tetra_rp/core/pool/ex.py,sha256=AZOrn9t_X5ycMl-tDg7-jcIURj_9kVmzn9_da8h1TFI,1273
11
11
  tetra_rp/core/pool/job.py,sha256=4bisW_ZwiQ2-qD5l0y9SbHcO4EQvSKimmBBU1fpI_YE,567
12
12
  tetra_rp/core/pool/worker.py,sha256=N4cOnf8MiDcPFH2XSMmSnnWMACZYUNnKWVhOx2aSxvM,478
13
- tetra_rp/core/resources/__init__.py,sha256=HKUNv3wktSHGMjNyZxolrYye4uNeWdKf8dF3eQJ51a8,751
13
+ tetra_rp/core/resources/__init__.py,sha256=UhIwo1Y6-tw5qsULamR296sQiztuz-oWrSTreqfmFSw,814
14
14
  tetra_rp/core/resources/base.py,sha256=UJeDiFN45aO1n5SBcxn56ohLhj-AWHoj0KO7mF4yJ_o,1440
15
15
  tetra_rp/core/resources/cloud.py,sha256=XJOWPfzYlDVJGHxgffcfpEaOKrWhGdi7AzTlaGuYj0o,70
16
+ tetra_rp/core/resources/constants.py,sha256=F1gPqFaXcCmfrbUSO9PQtUBv984TxFc3pySgVy-kXk8,158
16
17
  tetra_rp/core/resources/cpu.py,sha256=YIE-tKolSU3JJzpPB7ey-PbRdqKWsJZ_Ad4h2OYaaiA,1231
17
18
  tetra_rp/core/resources/environment.py,sha256=FC9kJCa8YLSar75AKUKqJYnNLrUdjZj8ZTOrspBrS00,1267
18
19
  tetra_rp/core/resources/gpu.py,sha256=2jIIMr8PNnlIAP8ZTKO8Imx-rdxXp2rbdSHJeVfjawk,1858
19
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
20
22
  tetra_rp/core/resources/resource_manager.py,sha256=kUVZDblfUzaG78S8FwOzu4rN6QSegUgQNK3fJ_X7l0w,2834
21
- tetra_rp/core/resources/serverless.py,sha256=HTln6FzjhINYSzEH_iGabdEY9u5BA6o6Q4sUlePe6Es,14850
23
+ tetra_rp/core/resources/serverless.py,sha256=lZMITI1cwOBVNX3dbFyR01AeRZ0eatNC4CRE7vwUrqs,14726
22
24
  tetra_rp/core/resources/template.py,sha256=UkflJXZFWIbQkLuUt4oRLAjn-yIpw9_mT2X1cAH69CU,3141
23
25
  tetra_rp/core/resources/utils.py,sha256=mgXfgz_NuHN_IC7TzMNdH9II-LMjxcDCG7syDTcPiGs,1721
24
26
  tetra_rp/core/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -31,7 +33,7 @@ tetra_rp/stubs/__init__.py,sha256=ozKsHs8q0T7o2qhQEquub9hqomh1Htys53mMraaRu2E,72
31
33
  tetra_rp/stubs/live_serverless.py,sha256=o1NH5XEwUD-27NXJsEGO0IwnuDp8iXwUiw5nZtaZZOI,4199
32
34
  tetra_rp/stubs/registry.py,sha256=V4m3CeXl8j1pguHuuflxqpWeBgVDQ93YkhxJbElyP7Q,2599
33
35
  tetra_rp/stubs/serverless.py,sha256=BM_a5Ml5VADBYu2WRNmo9qnicP8NnXDGl5ywifulbD0,947
34
- tetra_rp-0.5.5.dist-info/METADATA,sha256=q_m_qvvJylQiHwbX79OyLAc9oVfLLeFqWcEZc5oMrO8,28055
35
- tetra_rp-0.5.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
36
- tetra_rp-0.5.5.dist-info/top_level.txt,sha256=bBay7JTDwJXsTYvVjrwno9hnF-j0q272lk65f2AcPjU,9
37
- tetra_rp-0.5.5.dist-info/RECORD,,
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,,