tetra-rp 0.5.5__tar.gz → 0.6.0__tar.gz
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-0.5.5 → tetra_rp-0.6.0}/PKG-INFO +1 -1
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/pyproject.toml +1 -1
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/__init__.py +2 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/client.py +14 -2
- tetra_rp-0.6.0/src/tetra_rp/core/api/__init__.py +6 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/core/api/runpod.py +99 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/core/resources/__init__.py +2 -0
- tetra_rp-0.6.0/src/tetra_rp/core/resources/constants.py +4 -0
- tetra_rp-0.6.0/src/tetra_rp/core/resources/network_volume.py +98 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/core/resources/serverless.py +1 -5
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp.egg-info/PKG-INFO +1 -1
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp.egg-info/SOURCES.txt +2 -0
- tetra_rp-0.5.5/src/tetra_rp/core/api/__init__.py +0 -5
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/README.md +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/setup.cfg +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/core/__init__.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/core/pool/__init__.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/core/pool/cluster_manager.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/core/pool/dataclass.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/core/pool/ex.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/core/pool/job.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/core/pool/worker.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/core/resources/base.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/core/resources/cloud.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/core/resources/cpu.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/core/resources/environment.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/core/resources/gpu.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/core/resources/live_serverless.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/core/resources/resource_manager.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/core/resources/template.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/core/resources/utils.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/core/utils/__init__.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/core/utils/backoff.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/core/utils/json.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/core/utils/singleton.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/logger.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/protos/__init__.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/protos/remote_execution.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/stubs/__init__.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/stubs/live_serverless.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/stubs/registry.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp/stubs/serverless.py +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp.egg-info/dependency_links.txt +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp.egg-info/requires.txt +0 -0
- {tetra_rp-0.5.5 → tetra_rp-0.6.0}/src/tetra_rp.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tetra_rp
|
|
3
|
-
Version: 0.
|
|
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,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
|
|
@@ -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,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.
|
|
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
|
|
@@ -20,10 +20,12 @@ src/tetra_rp/core/pool/worker.py
|
|
|
20
20
|
src/tetra_rp/core/resources/__init__.py
|
|
21
21
|
src/tetra_rp/core/resources/base.py
|
|
22
22
|
src/tetra_rp/core/resources/cloud.py
|
|
23
|
+
src/tetra_rp/core/resources/constants.py
|
|
23
24
|
src/tetra_rp/core/resources/cpu.py
|
|
24
25
|
src/tetra_rp/core/resources/environment.py
|
|
25
26
|
src/tetra_rp/core/resources/gpu.py
|
|
26
27
|
src/tetra_rp/core/resources/live_serverless.py
|
|
28
|
+
src/tetra_rp/core/resources/network_volume.py
|
|
27
29
|
src/tetra_rp/core/resources/resource_manager.py
|
|
28
30
|
src/tetra_rp/core/resources/serverless.py
|
|
29
31
|
src/tetra_rp/core/resources/template.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|