kubetorch 0.2.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.

Potentially problematic release.


This version of kubetorch might be problematic. Click here for more details.

Files changed (93) hide show
  1. kubetorch/__init__.py +60 -0
  2. kubetorch/cli.py +1985 -0
  3. kubetorch/cli_utils.py +1025 -0
  4. kubetorch/config.py +453 -0
  5. kubetorch/constants.py +18 -0
  6. kubetorch/docs/Makefile +18 -0
  7. kubetorch/docs/__init__.py +0 -0
  8. kubetorch/docs/_ext/json_globaltoc.py +42 -0
  9. kubetorch/docs/api/cli.rst +10 -0
  10. kubetorch/docs/api/python/app.rst +21 -0
  11. kubetorch/docs/api/python/cls.rst +19 -0
  12. kubetorch/docs/api/python/compute.rst +25 -0
  13. kubetorch/docs/api/python/config.rst +11 -0
  14. kubetorch/docs/api/python/fn.rst +19 -0
  15. kubetorch/docs/api/python/image.rst +14 -0
  16. kubetorch/docs/api/python/secret.rst +18 -0
  17. kubetorch/docs/api/python/volumes.rst +13 -0
  18. kubetorch/docs/api/python.rst +101 -0
  19. kubetorch/docs/conf.py +69 -0
  20. kubetorch/docs/index.rst +20 -0
  21. kubetorch/docs/requirements.txt +5 -0
  22. kubetorch/globals.py +285 -0
  23. kubetorch/logger.py +59 -0
  24. kubetorch/resources/__init__.py +0 -0
  25. kubetorch/resources/callables/__init__.py +0 -0
  26. kubetorch/resources/callables/cls/__init__.py +0 -0
  27. kubetorch/resources/callables/cls/cls.py +157 -0
  28. kubetorch/resources/callables/fn/__init__.py +0 -0
  29. kubetorch/resources/callables/fn/fn.py +133 -0
  30. kubetorch/resources/callables/module.py +1416 -0
  31. kubetorch/resources/callables/utils.py +174 -0
  32. kubetorch/resources/compute/__init__.py +0 -0
  33. kubetorch/resources/compute/app.py +261 -0
  34. kubetorch/resources/compute/compute.py +2596 -0
  35. kubetorch/resources/compute/decorators.py +139 -0
  36. kubetorch/resources/compute/rbac.py +74 -0
  37. kubetorch/resources/compute/utils.py +1114 -0
  38. kubetorch/resources/compute/websocket.py +137 -0
  39. kubetorch/resources/images/__init__.py +1 -0
  40. kubetorch/resources/images/image.py +414 -0
  41. kubetorch/resources/images/images.py +74 -0
  42. kubetorch/resources/secrets/__init__.py +2 -0
  43. kubetorch/resources/secrets/kubernetes_secrets_client.py +412 -0
  44. kubetorch/resources/secrets/provider_secrets/__init__.py +0 -0
  45. kubetorch/resources/secrets/provider_secrets/anthropic_secret.py +12 -0
  46. kubetorch/resources/secrets/provider_secrets/aws_secret.py +16 -0
  47. kubetorch/resources/secrets/provider_secrets/azure_secret.py +14 -0
  48. kubetorch/resources/secrets/provider_secrets/cohere_secret.py +12 -0
  49. kubetorch/resources/secrets/provider_secrets/gcp_secret.py +16 -0
  50. kubetorch/resources/secrets/provider_secrets/github_secret.py +13 -0
  51. kubetorch/resources/secrets/provider_secrets/huggingface_secret.py +20 -0
  52. kubetorch/resources/secrets/provider_secrets/kubeconfig_secret.py +12 -0
  53. kubetorch/resources/secrets/provider_secrets/lambda_secret.py +13 -0
  54. kubetorch/resources/secrets/provider_secrets/langchain_secret.py +12 -0
  55. kubetorch/resources/secrets/provider_secrets/openai_secret.py +11 -0
  56. kubetorch/resources/secrets/provider_secrets/pinecone_secret.py +12 -0
  57. kubetorch/resources/secrets/provider_secrets/providers.py +93 -0
  58. kubetorch/resources/secrets/provider_secrets/ssh_secret.py +12 -0
  59. kubetorch/resources/secrets/provider_secrets/wandb_secret.py +11 -0
  60. kubetorch/resources/secrets/secret.py +238 -0
  61. kubetorch/resources/secrets/secret_factory.py +70 -0
  62. kubetorch/resources/secrets/utils.py +209 -0
  63. kubetorch/resources/volumes/__init__.py +0 -0
  64. kubetorch/resources/volumes/volume.py +365 -0
  65. kubetorch/servers/__init__.py +0 -0
  66. kubetorch/servers/http/__init__.py +0 -0
  67. kubetorch/servers/http/distributed_utils.py +3223 -0
  68. kubetorch/servers/http/http_client.py +730 -0
  69. kubetorch/servers/http/http_server.py +1788 -0
  70. kubetorch/servers/http/server_metrics.py +278 -0
  71. kubetorch/servers/http/utils.py +728 -0
  72. kubetorch/serving/__init__.py +0 -0
  73. kubetorch/serving/autoscaling.py +173 -0
  74. kubetorch/serving/base_service_manager.py +363 -0
  75. kubetorch/serving/constants.py +83 -0
  76. kubetorch/serving/deployment_service_manager.py +478 -0
  77. kubetorch/serving/knative_service_manager.py +519 -0
  78. kubetorch/serving/raycluster_service_manager.py +582 -0
  79. kubetorch/serving/service_manager.py +18 -0
  80. kubetorch/serving/templates/deployment_template.yaml +17 -0
  81. kubetorch/serving/templates/knative_service_template.yaml +19 -0
  82. kubetorch/serving/templates/kt_setup_template.sh.j2 +81 -0
  83. kubetorch/serving/templates/pod_template.yaml +194 -0
  84. kubetorch/serving/templates/raycluster_service_template.yaml +42 -0
  85. kubetorch/serving/templates/raycluster_template.yaml +35 -0
  86. kubetorch/serving/templates/service_template.yaml +21 -0
  87. kubetorch/serving/templates/workerset_template.yaml +36 -0
  88. kubetorch/serving/utils.py +377 -0
  89. kubetorch/utils.py +284 -0
  90. kubetorch-0.2.0.dist-info/METADATA +121 -0
  91. kubetorch-0.2.0.dist-info/RECORD +93 -0
  92. kubetorch-0.2.0.dist-info/WHEEL +4 -0
  93. kubetorch-0.2.0.dist-info/entry_points.txt +5 -0
@@ -0,0 +1,365 @@
1
+ import subprocess
2
+ import textwrap
3
+ import uuid
4
+
5
+ from functools import cached_property
6
+ from typing import Dict
7
+
8
+ from kubernetes import client
9
+ from kubernetes.client import ApiException, V1PersistentVolumeClaim
10
+
11
+ from kubetorch.constants import DEFAULT_VOLUME_ACCESS_MODE, KT_MOUNT_FOLDER
12
+ from kubetorch.globals import config
13
+ from kubetorch.logger import get_logger
14
+ from kubetorch.utils import load_kubeconfig
15
+
16
+ logger = get_logger(__name__)
17
+
18
+
19
+ class Volume:
20
+ """
21
+ Manages persistent storage for Kubetorch services and deployments.
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ name: str,
27
+ size: str,
28
+ storage_class: str = None,
29
+ mount_path: str = None,
30
+ access_mode: str = None,
31
+ namespace: str = None,
32
+ core_v1: client.CoreV1Api = None,
33
+ ):
34
+ """
35
+ Kubetorch Volume object, specifying persistent storage properties.
36
+
37
+ Args:
38
+ name (str): Name of the volume.
39
+ size (str): Size of the volume.
40
+ storage_class (str, optional): Storage class to use for the volume.
41
+ mount_path (str, optional): Mount path for the volume.
42
+ access_mode (str, optional): Access mode for the volume.
43
+ namespace (str, optional): Namespace for the volume.
44
+
45
+ Example:
46
+
47
+ .. code-block:: python
48
+
49
+ import kubetorch as kt
50
+
51
+ kt.Volume(name="my-data", size="5Gi"), # Standard volume (ReadWriteOnce)
52
+
53
+ # Shared volume (ReadWriteMany, requires JuiceFS or similar)
54
+ kt.Volume(name="shared-data", size="10Gi", storage_class="juicefs-sc-shared", access_mode="ReadWriteMany")
55
+
56
+ # uv cache
57
+ compute = kt.Compute(
58
+ cpus=".01",
59
+ env_vars={
60
+ "UV_CACHE_DIR": "/ktfs/kt-global-cache/uv_cache",
61
+ "HF_HOME": "/ktfs/kt-global-cache/hf_cache",
62
+ },
63
+ volumes=[kt.Volume("kt-global-cache", size="10Gi")],
64
+ )
65
+
66
+ """
67
+ self._storage_class = storage_class
68
+ if core_v1 is None:
69
+ load_kubeconfig()
70
+
71
+ self.size = size
72
+ self.access_mode = access_mode or DEFAULT_VOLUME_ACCESS_MODE
73
+ self.mount_path = mount_path or f"/{KT_MOUNT_FOLDER}/{name}"
74
+
75
+ self.name = name
76
+ self.namespace = namespace
77
+ self.core_v1 = core_v1 or client.CoreV1Api()
78
+
79
+ @property
80
+ def pvc_name(self) -> str:
81
+ return self.name
82
+
83
+ @cached_property
84
+ def storage_class(self) -> str:
85
+ """Get storage class - either specified or cluster default"""
86
+ if self._storage_class:
87
+ return self._storage_class
88
+
89
+ try:
90
+ storage_v1 = client.StorageV1Api()
91
+ storage_classes = storage_v1.list_storage_class().items
92
+
93
+ # If RWX is requested, prefer RWX-capable classes
94
+ if self.access_mode == "ReadWriteMany":
95
+ for sc in storage_classes:
96
+ provisioner = getattr(sc, "provisioner", "")
97
+ if provisioner in {
98
+ "csi.juicefs.com",
99
+ "nfs.csi.k8s.io",
100
+ "cephfs.csi.ceph.com",
101
+ }:
102
+ return sc.metadata.name
103
+ raise ValueError("No RWX-capable storage class found")
104
+
105
+ # Otherwise, pick the default StorageClass
106
+ for sc in storage_classes:
107
+ annotations = sc.metadata.annotations or {}
108
+ if (
109
+ annotations.get("storageclass.kubernetes.io/is-default-class")
110
+ == "true"
111
+ ):
112
+ logger.info(f"Using default storage class: {sc.metadata.name}")
113
+ return sc.metadata.name
114
+
115
+ # No default found, fall back to first available
116
+ available_classes = [sc.metadata.name for sc in storage_classes]
117
+ first_sc = available_classes[0]
118
+ if len(available_classes) == 1:
119
+ logger.info(
120
+ f"No default storage class found, using only available one: {first_sc}"
121
+ )
122
+ else:
123
+ logger.warning(
124
+ f"No default storage class found, using first available: {first_sc}. "
125
+ f"Available: {available_classes}. Consider setting a default or specifying storage_class parameter."
126
+ )
127
+ return first_sc
128
+
129
+ except Exception as e:
130
+ logger.error(f"Failed to get storage classes: {e}")
131
+ raise
132
+
133
+ @classmethod
134
+ def from_name(
135
+ cls,
136
+ name: str,
137
+ create_if_missing: bool = False,
138
+ namespace: str = None,
139
+ core_v1: client.CoreV1Api = None,
140
+ ) -> "Volume":
141
+ """Get existing volume or optionally create it"""
142
+ if core_v1 is None:
143
+ load_kubeconfig()
144
+ core_v1 = client.CoreV1Api()
145
+
146
+ namespace = namespace or config.namespace
147
+ pvc_name = name
148
+
149
+ try:
150
+ pvc = core_v1.read_namespaced_persistent_volume_claim(pvc_name, namespace)
151
+
152
+ storage_class = pvc.spec.storage_class_name
153
+ size = pvc.spec.resources.requests.get("storage")
154
+ access_mode = (
155
+ pvc.spec.access_modes[0]
156
+ if pvc.spec.access_modes
157
+ else DEFAULT_VOLUME_ACCESS_MODE
158
+ )
159
+
160
+ annotations = pvc.metadata.annotations or {}
161
+ mount_path = annotations.get(
162
+ "kubetorch.com/mount-path", f"/{KT_MOUNT_FOLDER}/{name}"
163
+ )
164
+
165
+ # Create Volume with actual attributes from PVC
166
+ vol = cls(
167
+ name=name,
168
+ storage_class=storage_class,
169
+ mount_path=mount_path,
170
+ size=size,
171
+ access_mode=access_mode,
172
+ namespace=namespace,
173
+ core_v1=core_v1,
174
+ )
175
+
176
+ logger.debug(
177
+ f"Loaded existing PVC {pvc_name} with storage_class={storage_class}"
178
+ )
179
+ return vol
180
+
181
+ except ApiException as e:
182
+ if e.status == 404:
183
+ # PVC doesn't exist
184
+ if create_if_missing:
185
+ vol = cls(name, namespace=namespace, core_v1=core_v1)
186
+ vol.create()
187
+ return vol
188
+ else:
189
+ raise ValueError(
190
+ f"Volume '{name}' (PVC: {pvc_name}) does not exist in namespace '{namespace}'"
191
+ )
192
+ else:
193
+ # Some other API error
194
+ raise
195
+
196
+ def config(self) -> Dict[str, str]:
197
+ """Get configuration for this volume"""
198
+ return {
199
+ "name": self.name,
200
+ "size": self.size,
201
+ "access_mode": self.access_mode,
202
+ "mount_path": self.mount_path,
203
+ "storage_class": self.storage_class,
204
+ "namespace": self.namespace,
205
+ }
206
+
207
+ def pod_template_spec(self) -> dict:
208
+ """Convert to Kubernetes volume spec for pod template"""
209
+ return {
210
+ "name": self.name,
211
+ "persistentVolumeClaim": {"claimName": self.pvc_name},
212
+ }
213
+
214
+ def create(self) -> V1PersistentVolumeClaim:
215
+ """Create PVC if it doesn't exist"""
216
+ try:
217
+ try:
218
+ # Check if PVC already exists
219
+ existing_pvc = self.core_v1.read_namespaced_persistent_volume_claim(
220
+ name=self.pvc_name, namespace=self.namespace
221
+ )
222
+ logger.debug(
223
+ f"PVC {self.pvc_name} already exists in namespace {self.namespace}"
224
+ )
225
+ return existing_pvc
226
+ except ApiException as e:
227
+ if e.status != 404:
228
+ # Some other error occurred
229
+ raise
230
+
231
+ logger.info(f"Creating new PVC with name: {self.pvc_name}")
232
+
233
+ storage_class_name = self.storage_class
234
+
235
+ pvc_spec = client.V1PersistentVolumeClaimSpec(
236
+ access_modes=[self.access_mode],
237
+ resources=client.V1ResourceRequirements(
238
+ requests={"storage": self.size}
239
+ ),
240
+ storage_class_name=storage_class_name,
241
+ )
242
+
243
+ pvc_metadata = client.V1ObjectMeta(
244
+ name=self.pvc_name,
245
+ labels={
246
+ "app": "kubetorch",
247
+ "kubetorch.com/volume": self.name,
248
+ },
249
+ annotations={"kubetorch.com/mount-path": self.mount_path},
250
+ )
251
+
252
+ pvc = client.V1PersistentVolumeClaim(
253
+ api_version="v1",
254
+ kind="PersistentVolumeClaim",
255
+ metadata=pvc_metadata,
256
+ spec=pvc_spec,
257
+ )
258
+
259
+ created_pvc = self.core_v1.create_namespaced_persistent_volume_claim(
260
+ namespace=self.namespace, body=pvc
261
+ )
262
+
263
+ logger.info(
264
+ f"Successfully created PVC {self.pvc_name} in namespace {self.namespace} with "
265
+ f"storage class {storage_class_name}"
266
+ )
267
+ return created_pvc
268
+
269
+ except Exception as e:
270
+ logger.error(f"Failed to create PVC {self.pvc_name}: {e}")
271
+ raise
272
+
273
+ def delete(self) -> None:
274
+ """Delete the PVC"""
275
+ try:
276
+ self.core_v1.delete_namespaced_persistent_volume_claim(
277
+ name=self.pvc_name, namespace=self.namespace
278
+ )
279
+ logger.debug(f"Successfully deleted PVC {self.pvc_name}")
280
+ except ApiException as e:
281
+ if e.status == 404:
282
+ logger.warning(f"PVC {self.pvc_name} not found")
283
+ else:
284
+ logger.error(f"Failed to delete PVC {self.pvc_name}: {e}")
285
+ raise
286
+
287
+ def exists(self) -> bool:
288
+ """Check if the PVC exists"""
289
+ try:
290
+ self.core_v1.read_namespaced_persistent_volume_claim(
291
+ name=self.pvc_name, namespace=self.namespace
292
+ )
293
+ return True
294
+ except ApiException as e:
295
+ if e.status == 404:
296
+ return False
297
+ else:
298
+ # Some other API error, re-raise
299
+ raise
300
+
301
+ def ssh(self, image: str = "alpine:latest"):
302
+ """
303
+ Launch an interactive debug shell with this volume mounted.
304
+
305
+ This method creates a temporary Kubernetes pod that mounts the
306
+ PersistentVolumeClaim (PVC) backing this Volume at the same path
307
+ (`self.mount_path`) used by Kubetorch services.
308
+
309
+ Args:
310
+ image (str, optional): Container image to use for the debug pod.
311
+ Must include a shell (e.g., `alpine:3.18`, `ubuntu:22.04`,
312
+ or a custom tools image). Defaults to `alpine:latest`.
313
+
314
+ Example:
315
+
316
+ .. code-block:: python
317
+
318
+ import kubetorch as kt
319
+
320
+ vol = kt.Volume.from_name("kt-global-cache")
321
+ vol.ssh()
322
+ """
323
+ pod_name = f"debug-{self.name}-{uuid.uuid4().hex[:6]}"
324
+
325
+ cmd = [
326
+ "kubectl",
327
+ "run",
328
+ pod_name,
329
+ "--rm",
330
+ "-it",
331
+ "--namespace",
332
+ self.namespace,
333
+ "--image",
334
+ image,
335
+ "--restart=Never",
336
+ "--overrides",
337
+ textwrap.dedent(
338
+ f"""
339
+ {{
340
+ "apiVersion": "v1",
341
+ "spec": {{
342
+ "containers": [{{
343
+ "name": "debug",
344
+ "image": "{image}",
345
+ "stdin": true,
346
+ "tty": true,
347
+ "volumeMounts": [{{
348
+ "name": "vol",
349
+ "mountPath": "{self.mount_path}"
350
+ }}]
351
+ }}],
352
+ "volumes": [{{
353
+ "name": "vol",
354
+ "persistentVolumeClaim": {{
355
+ "claimName": "{self.pvc_name}"
356
+ }}
357
+ }}]
358
+ }}
359
+ }}
360
+ """
361
+ ).strip(),
362
+ ]
363
+
364
+ # Suppress noisy "write on closed stream" error when exiting
365
+ subprocess.run(cmd, stderr=subprocess.DEVNULL)
File without changes
File without changes