datacrunch 1.16.0__py3-none-any.whl → 1.17.1__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.
Files changed (37) hide show
  1. datacrunch/__init__.py +52 -2
  2. datacrunch/datacrunch.py +44 -81
  3. datacrunch-1.17.1.dist-info/METADATA +30 -0
  4. datacrunch-1.17.1.dist-info/RECORD +5 -0
  5. {datacrunch-1.16.0.dist-info → datacrunch-1.17.1.dist-info}/WHEEL +1 -1
  6. datacrunch/InferenceClient/__init__.py +0 -3
  7. datacrunch/InferenceClient/inference_client.py +0 -514
  8. datacrunch/_version.py +0 -6
  9. datacrunch/authentication/__init__.py +0 -0
  10. datacrunch/authentication/authentication.py +0 -105
  11. datacrunch/balance/__init__.py +0 -0
  12. datacrunch/balance/balance.py +0 -50
  13. datacrunch/constants.py +0 -109
  14. datacrunch/containers/__init__.py +0 -33
  15. datacrunch/containers/containers.py +0 -1109
  16. datacrunch/exceptions.py +0 -29
  17. datacrunch/helpers.py +0 -18
  18. datacrunch/http_client/__init__.py +0 -0
  19. datacrunch/http_client/http_client.py +0 -241
  20. datacrunch/images/__init__.py +0 -0
  21. datacrunch/images/images.py +0 -93
  22. datacrunch/instance_types/__init__.py +0 -0
  23. datacrunch/instance_types/instance_types.py +0 -195
  24. datacrunch/instances/__init__.py +0 -0
  25. datacrunch/instances/instances.py +0 -259
  26. datacrunch/locations/__init__.py +0 -0
  27. datacrunch/locations/locations.py +0 -15
  28. datacrunch/ssh_keys/__init__.py +0 -0
  29. datacrunch/ssh_keys/ssh_keys.py +0 -111
  30. datacrunch/startup_scripts/__init__.py +0 -0
  31. datacrunch/startup_scripts/startup_scripts.py +0 -115
  32. datacrunch/volume_types/__init__.py +0 -0
  33. datacrunch/volume_types/volume_types.py +0 -68
  34. datacrunch/volumes/__init__.py +0 -0
  35. datacrunch/volumes/volumes.py +0 -385
  36. datacrunch-1.16.0.dist-info/METADATA +0 -182
  37. datacrunch-1.16.0.dist-info/RECORD +0 -35
@@ -1,1109 +0,0 @@
1
- """Container deployment and management service for DataCrunch.
2
-
3
- This module provides functionality for managing container deployments, including
4
- creation, updates, deletion, and monitoring of containerized applications.
5
- """
6
-
7
- import base64
8
- import os
9
- from dataclasses import dataclass, field
10
- from dataclasses_json import dataclass_json, Undefined # type: ignore
11
- from typing import List, Optional, Dict, Any, Union
12
- from enum import Enum
13
-
14
- from datacrunch.http_client.http_client import HTTPClient
15
- from datacrunch.InferenceClient import InferenceClient, InferenceResponse
16
-
17
-
18
- # API endpoints
19
- CONTAINER_DEPLOYMENTS_ENDPOINT = '/container-deployments'
20
- SERVERLESS_COMPUTE_RESOURCES_ENDPOINT = '/serverless-compute-resources'
21
- CONTAINER_REGISTRY_CREDENTIALS_ENDPOINT = '/container-registry-credentials'
22
- SECRETS_ENDPOINT = '/secrets'
23
- FILESET_SECRETS_ENDPOINT = '/file-secrets'
24
-
25
-
26
- class EnvVarType(str, Enum):
27
- """Types of environment variables that can be set in containers."""
28
-
29
- PLAIN = 'plain'
30
- SECRET = 'secret'
31
-
32
-
33
- class SecretType(str, Enum):
34
- """Types of secrets that can be set in containers."""
35
-
36
- GENERIC = 'generic' # Regular secret, can be used in env vars
37
- FILESET = 'file-secret' # A file secret that can be mounted into the container
38
-
39
-
40
- class VolumeMountType(str, Enum):
41
- """Types of volume mounts that can be configured for containers."""
42
-
43
- SCRATCH = 'scratch'
44
- SECRET = 'secret'
45
- MEMORY = 'memory'
46
- SHARED = 'shared'
47
-
48
-
49
- class ContainerRegistryType(str, Enum):
50
- """Supported container registry types."""
51
-
52
- GCR = 'gcr'
53
- DOCKERHUB = 'dockerhub'
54
- GITHUB = 'ghcr'
55
- AWS_ECR = 'aws-ecr'
56
- CUSTOM = 'custom'
57
-
58
-
59
- class ContainerDeploymentStatus(str, Enum):
60
- """Possible states of a container deployment."""
61
-
62
- INITIALIZING = 'initializing'
63
- HEALTHY = 'healthy'
64
- DEGRADED = 'degraded'
65
- UNHEALTHY = 'unhealthy'
66
- PAUSED = 'paused'
67
- QUOTA_REACHED = 'quota_reached'
68
- IMAGE_PULLING = 'image_pulling'
69
- VERSION_UPDATING = 'version_updating'
70
-
71
-
72
- @dataclass_json
73
- @dataclass
74
- class HealthcheckSettings:
75
- """Configuration for container health checking.
76
-
77
- Attributes:
78
- enabled: Whether health checking is enabled.
79
- port: Port number to perform health check on.
80
- path: HTTP path to perform health check on.
81
- """
82
-
83
- enabled: bool = True
84
- port: Optional[int] = None
85
- path: Optional[str] = None
86
-
87
-
88
- @dataclass_json
89
- @dataclass
90
- class EntrypointOverridesSettings:
91
- """Configuration for overriding container entrypoint and command.
92
-
93
- Attributes:
94
- enabled: Whether entrypoint overrides are enabled.
95
- entrypoint: List of strings forming the entrypoint command.
96
- cmd: List of strings forming the command arguments.
97
- """
98
-
99
- enabled: bool = True
100
- entrypoint: Optional[List[str]] = None
101
- cmd: Optional[List[str]] = None
102
-
103
-
104
- @dataclass_json
105
- @dataclass
106
- class EnvVar:
107
- """Environment variable configuration for containers.
108
-
109
- Attributes:
110
- name: Name of the environment variable.
111
- value_or_reference_to_secret: Direct value or reference to a secret.
112
- type: Type of the environment variable.
113
- """
114
-
115
- name: str
116
- value_or_reference_to_secret: str
117
- type: EnvVarType
118
-
119
-
120
- @dataclass_json(undefined=Undefined.EXCLUDE)
121
- @dataclass
122
- class VolumeMount:
123
- """Base class for volume mount configurations.
124
-
125
- Attributes:
126
- type: Type of volume mount.
127
- mount_path: Path where the volume should be mounted in the container.
128
- size_in_mb: Size of the volume in megabytes. Deprecated: use MemoryMount for memory volumes instead.
129
- """
130
-
131
- type: VolumeMountType
132
- mount_path: str
133
- # Deprecated: use MemoryMount for memory volumes instead.
134
- size_in_mb: Optional[int] = field(default=None, kw_only=True)
135
-
136
-
137
- @dataclass_json(undefined=Undefined.EXCLUDE)
138
- @dataclass
139
- class GeneralStorageMount(VolumeMount):
140
- """General storage volume mount configuration."""
141
-
142
- def __init__(self, mount_path: str):
143
- """Initialize a general scratch volume mount.
144
-
145
- Args:
146
- mount_path: Path where the volume should be mounted in the container.
147
- """
148
- super().__init__(type=VolumeMountType.SCRATCH, mount_path=mount_path)
149
-
150
-
151
- @dataclass_json(undefined=Undefined.EXCLUDE)
152
- @dataclass
153
- class SecretMount(VolumeMount):
154
- """Secret volume mount configuration.
155
-
156
- A secret volume mount allows mounting secret files into the container.
157
-
158
- Attributes:
159
- secret_name: The name of the fileset secret to mount. This secret must be created in advance, for example using `create_fileset_secret_from_file_paths`
160
- file_names: List of file names that are part of the fileset secret.
161
- """
162
-
163
- secret_name: str
164
- file_names: Optional[List[str]] = None
165
-
166
- def __init__(self, mount_path: str, secret_name: str, file_names: Optional[List[str]] = None):
167
- self.secret_name = secret_name
168
- self.file_names = file_names
169
- super().__init__(type=VolumeMountType.SECRET, mount_path=mount_path)
170
-
171
-
172
- @dataclass_json(undefined=Undefined.EXCLUDE)
173
- @dataclass
174
- class MemoryMount(VolumeMount):
175
- """Memory volume mount configuration.
176
-
177
- A memory volume mount provides high-speed, ephemeral in-memory storage inside your container.
178
- The mount path is currently hardcoded to /dev/shm and cannot be changed.
179
-
180
- Attributes:
181
- size_in_mb: Size of the memory volume in megabytes.
182
- """
183
-
184
- size_in_mb: int
185
-
186
- def __init__(self, size_in_mb: int):
187
- super().__init__(type=VolumeMountType.MEMORY, mount_path='/dev/shm')
188
- self.size_in_mb = size_in_mb
189
-
190
-
191
- @dataclass_json(undefined=Undefined.EXCLUDE)
192
- @dataclass
193
- class SharedFileSystemMount(VolumeMount):
194
- """Shared filesystem volume mount configuration.
195
-
196
- A shared filesystem volume mount allows mounting a shared filesystem into the container.
197
- """
198
-
199
- volume_id: str # The ID of the shared filesystem volume to mount, needs to be created first
200
-
201
- def __init__(self, mount_path: str, volume_id: str):
202
- super().__init__(type=VolumeMountType.SHARED, mount_path=mount_path)
203
- self.volume_id = volume_id
204
-
205
-
206
- @dataclass_json
207
- @dataclass
208
- class Container:
209
- """Container configuration for deployment creation and updates.
210
-
211
- Attributes:
212
- image: Container image to use.
213
- exposed_port: Port to expose from the container.
214
- name: Name of the container (system-managed, read-only).
215
- healthcheck: Optional health check configuration.
216
- entrypoint_overrides: Optional entrypoint override settings.
217
- env: Optional list of environment variables.
218
- volume_mounts: Optional list of volume mounts.
219
- """
220
-
221
- image: Union[str, dict]
222
- exposed_port: int
223
- name: Optional[str] = None
224
- healthcheck: Optional[HealthcheckSettings] = None
225
- entrypoint_overrides: Optional[EntrypointOverridesSettings] = None
226
- env: Optional[List[EnvVar]] = None
227
- volume_mounts: Optional[List[VolumeMount]] = None
228
-
229
-
230
- @dataclass_json
231
- @dataclass
232
- class ContainerRegistryCredentials:
233
- """Credentials for accessing a container registry.
234
-
235
- Attributes:
236
- name: Name of the credentials.
237
- """
238
-
239
- name: str
240
-
241
-
242
- @dataclass_json
243
- @dataclass
244
- class ContainerRegistrySettings:
245
- """Settings for container registry access.
246
-
247
- Attributes:
248
- is_private: Whether the registry is private.
249
- credentials: Optional credentials for accessing private registry.
250
- """
251
-
252
- is_private: bool
253
- credentials: Optional[ContainerRegistryCredentials] = None
254
-
255
-
256
- @dataclass_json
257
- @dataclass
258
- class ComputeResource:
259
- """Compute resource configuration.
260
-
261
- Attributes:
262
- name: Name of the compute resource.
263
- size: Size of the compute resource.
264
- is_available: Whether the compute resource is currently available.
265
- """
266
-
267
- name: str
268
- size: int
269
- # Made optional since it's only used in API responses
270
- is_available: Optional[bool] = None
271
-
272
-
273
- @dataclass_json
274
- @dataclass
275
- class ScalingPolicy:
276
- """Policy for controlling scaling behavior.
277
-
278
- Attributes:
279
- delay_seconds: Number of seconds to wait before applying scaling action.
280
- """
281
-
282
- delay_seconds: int
283
-
284
-
285
- @dataclass_json
286
- @dataclass
287
- class QueueLoadScalingTrigger:
288
- """Trigger for scaling based on queue load.
289
-
290
- Attributes:
291
- threshold: Queue load threshold that triggers scaling.
292
- """
293
-
294
- threshold: float
295
-
296
-
297
- @dataclass_json
298
- @dataclass
299
- class UtilizationScalingTrigger:
300
- """Trigger for scaling based on resource utilization.
301
-
302
- Attributes:
303
- enabled: Whether this trigger is enabled.
304
- threshold: Utilization threshold that triggers scaling.
305
- """
306
-
307
- enabled: bool
308
- threshold: Optional[float] = None
309
-
310
-
311
- @dataclass_json
312
- @dataclass
313
- class ScalingTriggers:
314
- """Collection of triggers that can cause scaling actions.
315
-
316
- Attributes:
317
- queue_load: Optional trigger based on queue load.
318
- cpu_utilization: Optional trigger based on CPU utilization.
319
- gpu_utilization: Optional trigger based on GPU utilization.
320
- """
321
-
322
- queue_load: Optional[QueueLoadScalingTrigger] = None
323
- cpu_utilization: Optional[UtilizationScalingTrigger] = None
324
- gpu_utilization: Optional[UtilizationScalingTrigger] = None
325
-
326
-
327
- @dataclass_json
328
- @dataclass
329
- class ScalingOptions:
330
- """Configuration for automatic scaling behavior.
331
-
332
- Attributes:
333
- min_replica_count: Minimum number of replicas to maintain.
334
- max_replica_count: Maximum number of replicas allowed.
335
- scale_down_policy: Policy for scaling down replicas.
336
- scale_up_policy: Policy for scaling up replicas.
337
- queue_message_ttl_seconds: Time-to-live for queue messages in seconds.
338
- concurrent_requests_per_replica: Number of concurrent requests each replica can handle.
339
- scaling_triggers: Configuration for various scaling triggers.
340
- """
341
-
342
- min_replica_count: int
343
- max_replica_count: int
344
- scale_down_policy: ScalingPolicy
345
- scale_up_policy: ScalingPolicy
346
- queue_message_ttl_seconds: int
347
- concurrent_requests_per_replica: int
348
- scaling_triggers: ScalingTriggers
349
-
350
-
351
- @dataclass_json(undefined=Undefined.EXCLUDE)
352
- @dataclass
353
- class Deployment:
354
- """Configuration for creating or updating a container deployment.
355
-
356
- Attributes:
357
- name: Name of the deployment.
358
- container_registry_settings: Settings for accessing container registry.
359
- containers: List of container specifications in the deployment.
360
- compute: Compute resource configuration.
361
- is_spot: Whether is spot deployment.
362
- endpoint_base_url: Optional base URL for the deployment endpoint.
363
- scaling: Optional scaling configuration.
364
- created_at: Optional timestamp when the deployment was created.
365
- """
366
-
367
- name: str
368
- containers: List[Container]
369
- compute: ComputeResource
370
- container_registry_settings: ContainerRegistrySettings = field(
371
- default_factory=lambda: ContainerRegistrySettings(is_private=False)
372
- )
373
- is_spot: bool = False
374
- endpoint_base_url: Optional[str] = None
375
- scaling: Optional[ScalingOptions] = None
376
- created_at: Optional[str] = None
377
-
378
- _inference_client: Optional[InferenceClient] = None
379
-
380
- def __str__(self):
381
- """Returns a string representation of the deployment, excluding sensitive information.
382
-
383
- Returns:
384
- str: A formatted string representation of the deployment.
385
- """
386
- # Get all attributes except _inference_client
387
- attrs = {k: v for k, v in self.__dict__.items() if k != '_inference_client'}
388
- # Format each attribute
389
- attr_strs = [f'{k}={repr(v)}' for k, v in attrs.items()]
390
- return f'Deployment({", ".join(attr_strs)})'
391
-
392
- def __repr__(self):
393
- """Returns a repr representation of the deployment, excluding sensitive information.
394
-
395
- Returns:
396
- str: A formatted string representation of the deployment.
397
- """
398
- return self.__str__()
399
-
400
- @classmethod
401
- def from_dict_with_inference_key(
402
- cls, data: Dict[str, Any], inference_key: str = None
403
- ) -> 'Deployment':
404
- """Creates a Deployment instance from a dictionary with an inference key.
405
-
406
- Args:
407
- data: Dictionary containing deployment data.
408
- inference_key: Inference key to set on the deployment.
409
-
410
- Returns:
411
- Deployment: A new Deployment instance with the inference client initialized.
412
- """
413
- deployment = Deployment.from_dict(data, infer_missing=True)
414
- if inference_key and deployment.endpoint_base_url:
415
- deployment._inference_client = InferenceClient(
416
- inference_key=inference_key,
417
- endpoint_base_url=deployment.endpoint_base_url,
418
- )
419
- return deployment
420
-
421
- def set_inference_client(self, inference_key: str) -> None:
422
- """Sets the inference client for this deployment.
423
-
424
- Args:
425
- inference_key: The inference key to use for authentication.
426
-
427
- Raises:
428
- ValueError: If endpoint_base_url is not set.
429
- """
430
- if self.endpoint_base_url is None:
431
- raise ValueError('Endpoint base URL must be set to use inference client')
432
- self._inference_client = InferenceClient(
433
- inference_key=inference_key, endpoint_base_url=self.endpoint_base_url
434
- )
435
-
436
- def _validate_inference_client(self) -> None:
437
- """Validates that the inference client is initialized.
438
-
439
- Raises:
440
- ValueError: If inference client is not initialized.
441
- """
442
- if self._inference_client is None:
443
- raise ValueError(
444
- 'Inference client not initialized. Use from_dict_with_inference_key or set_inference_client to initialize inference capabilities.'
445
- )
446
-
447
- def run_sync(
448
- self,
449
- data: Dict[str, Any],
450
- path: str = '',
451
- timeout_seconds: int = 60 * 5,
452
- headers: Optional[Dict[str, str]] = None,
453
- http_method: str = 'POST',
454
- stream: bool = False,
455
- ) -> InferenceResponse:
456
- """Runs a synchronous inference request.
457
-
458
- Args:
459
- data: The data to send in the request.
460
- path: The endpoint path to send the request to.
461
- timeout_seconds: Maximum time to wait for the response.
462
- headers: Optional headers to include in the request.
463
- http_method: The HTTP method to use for the request.
464
- stream: Whether to stream the response.
465
-
466
- Returns:
467
- InferenceResponse: The response from the inference request.
468
-
469
- Raises:
470
- ValueError: If the inference client is not initialized.
471
- """
472
- self._validate_inference_client()
473
- return self._inference_client.run_sync(
474
- data, path, timeout_seconds, headers, http_method, stream
475
- )
476
-
477
- def run(
478
- self,
479
- data: Dict[str, Any],
480
- path: str = '',
481
- timeout_seconds: int = 60 * 5,
482
- headers: Optional[Dict[str, str]] = None,
483
- http_method: str = 'POST',
484
- stream: bool = False,
485
- ):
486
- """Runs an asynchronous inference request.
487
-
488
- Args:
489
- data: The data to send in the request.
490
- path: The endpoint path to send the request to.
491
- timeout_seconds: Maximum time to wait for the response.
492
- headers: Optional headers to include in the request.
493
- http_method: The HTTP method to use for the request.
494
- stream: Whether to stream the response.
495
-
496
- Returns:
497
- The response from the inference request.
498
-
499
- Raises:
500
- ValueError: If the inference client is not initialized.
501
- """
502
- self._validate_inference_client()
503
- return self._inference_client.run(data, path, timeout_seconds, headers, http_method, stream)
504
-
505
- def health(self):
506
- """Checks the health of the deployed application.
507
-
508
- Returns:
509
- The health check response.
510
-
511
- Raises:
512
- ValueError: If the inference client is not initialized.
513
- """
514
- self._validate_inference_client()
515
- # build healthcheck path
516
- healthcheck_path = '/health'
517
- if (
518
- self.containers
519
- and self.containers[0].healthcheck
520
- and self.containers[0].healthcheck.path
521
- ):
522
- healthcheck_path = self.containers[0].healthcheck.path
523
-
524
- return self._inference_client.health(healthcheck_path)
525
-
526
- # Function alias
527
- healthcheck = health
528
-
529
-
530
- @dataclass_json
531
- @dataclass
532
- class ReplicaInfo:
533
- """Information about a deployment replica.
534
-
535
- Attributes:
536
- id: Unique identifier of the replica.
537
- status: Current status of the replica.
538
- started_at: Timestamp when the replica was started.
539
- """
540
-
541
- id: str
542
- status: str
543
- started_at: str
544
-
545
-
546
- @dataclass_json
547
- @dataclass
548
- class Secret:
549
- """A secret model class.
550
-
551
- Attributes:
552
- name: Name of the secret.
553
- created_at: Timestamp when the secret was created.
554
- secret_type: Type of the secret.
555
- """
556
-
557
- name: str
558
- created_at: str
559
- secret_type: SecretType
560
-
561
-
562
- @dataclass_json
563
- @dataclass
564
- class RegistryCredential:
565
- """A container registry credential model class.
566
-
567
- Attributes:
568
- name: Name of the registry credential.
569
- created_at: Timestamp when the credential was created.
570
- """
571
-
572
- name: str
573
- created_at: str
574
-
575
-
576
- @dataclass_json
577
- @dataclass
578
- class BaseRegistryCredentials:
579
- """Base class for registry credentials.
580
-
581
- Attributes:
582
- name: Name of the registry credential.
583
- type: Type of the container registry.
584
- """
585
-
586
- name: str
587
- type: ContainerRegistryType
588
-
589
-
590
- @dataclass_json
591
- @dataclass
592
- class DockerHubCredentials(BaseRegistryCredentials):
593
- """Credentials for DockerHub registry.
594
-
595
- Attributes:
596
- username: DockerHub username.
597
- access_token: DockerHub access token.
598
- """
599
-
600
- username: str
601
- access_token: str
602
-
603
- def __init__(self, name: str, username: str, access_token: str):
604
- """Initializes DockerHub credentials.
605
-
606
- Args:
607
- name: Name of the credentials.
608
- username: DockerHub username.
609
- access_token: DockerHub access token.
610
- """
611
- super().__init__(name=name, type=ContainerRegistryType.DOCKERHUB)
612
- self.username = username
613
- self.access_token = access_token
614
-
615
-
616
- @dataclass_json
617
- @dataclass
618
- class GithubCredentials(BaseRegistryCredentials):
619
- """Credentials for GitHub Container Registry.
620
-
621
- Attributes:
622
- username: GitHub username.
623
- access_token: GitHub access token.
624
- """
625
-
626
- username: str
627
- access_token: str
628
-
629
- def __init__(self, name: str, username: str, access_token: str):
630
- """Initializes GitHub credentials.
631
-
632
- Args:
633
- name: Name of the credentials.
634
- username: GitHub username.
635
- access_token: GitHub access token.
636
- """
637
- super().__init__(name=name, type=ContainerRegistryType.GITHUB)
638
- self.username = username
639
- self.access_token = access_token
640
-
641
-
642
- @dataclass_json
643
- @dataclass
644
- class GCRCredentials(BaseRegistryCredentials):
645
- """Credentials for Google Container Registry.
646
-
647
- Attributes:
648
- service_account_key: GCP service account key JSON.
649
- """
650
-
651
- service_account_key: str
652
-
653
- def __init__(self, name: str, service_account_key: str):
654
- """Initializes GCR credentials.
655
-
656
- Args:
657
- name: Name of the credentials.
658
- service_account_key: GCP service account key JSON.
659
- """
660
- super().__init__(name=name, type=ContainerRegistryType.GCR)
661
- self.service_account_key = service_account_key
662
-
663
-
664
- @dataclass_json
665
- @dataclass
666
- class AWSECRCredentials(BaseRegistryCredentials):
667
- """Credentials for AWS Elastic Container Registry.
668
-
669
- Attributes:
670
- access_key_id: AWS access key ID.
671
- secret_access_key: AWS secret access key.
672
- region: AWS region.
673
- ecr_repo: ECR repository name.
674
- """
675
-
676
- access_key_id: str
677
- secret_access_key: str
678
- region: str
679
- ecr_repo: str
680
-
681
- def __init__(
682
- self,
683
- name: str,
684
- access_key_id: str,
685
- secret_access_key: str,
686
- region: str,
687
- ecr_repo: str,
688
- ):
689
- """Initializes AWS ECR credentials.
690
-
691
- Args:
692
- name: Name of the credentials.
693
- access_key_id: AWS access key ID.
694
- secret_access_key: AWS secret access key.
695
- region: AWS region.
696
- ecr_repo: ECR repository name.
697
- """
698
- super().__init__(name=name, type=ContainerRegistryType.AWS_ECR)
699
- self.access_key_id = access_key_id
700
- self.secret_access_key = secret_access_key
701
- self.region = region
702
- self.ecr_repo = ecr_repo
703
-
704
-
705
- @dataclass_json
706
- @dataclass
707
- class CustomRegistryCredentials(BaseRegistryCredentials):
708
- """Credentials for custom container registries.
709
-
710
- Attributes:
711
- docker_config_json: Docker config JSON containing registry credentials.
712
- """
713
-
714
- docker_config_json: str
715
-
716
- def __init__(self, name: str, docker_config_json: str):
717
- """Initializes custom registry credentials.
718
-
719
- Args:
720
- name: Name of the credentials.
721
- docker_config_json: Docker config JSON containing registry credentials.
722
- """
723
- super().__init__(name=name, type=ContainerRegistryType.CUSTOM)
724
- self.docker_config_json = docker_config_json
725
-
726
-
727
- class ContainersService:
728
- """Service for managing container deployments.
729
-
730
- This class provides methods for interacting with the DataCrunch container
731
- deployment API, including CRUD operations for deployments and related resources.
732
- """
733
-
734
- def __init__(self, http_client: HTTPClient, inference_key: str = None) -> None:
735
- """Initializes the containers service.
736
-
737
- Args:
738
- http_client: HTTP client for making API requests.
739
- inference_key: Optional inference key for authenticating inference requests.
740
- """
741
- self.client = http_client
742
- self._inference_key = inference_key
743
-
744
- def get_deployments(self) -> List[Deployment]:
745
- """Retrieves all container deployments.
746
-
747
- Returns:
748
- List[Deployment]: List of all deployments.
749
- """
750
- response = self.client.get(CONTAINER_DEPLOYMENTS_ENDPOINT)
751
- return [
752
- Deployment.from_dict_with_inference_key(deployment, self._inference_key)
753
- for deployment in response.json()
754
- ]
755
-
756
- def get_deployment_by_name(self, deployment_name: str) -> Deployment:
757
- """Retrieves a specific deployment by name.
758
-
759
- Args:
760
- deployment_name: Name of the deployment to retrieve.
761
-
762
- Returns:
763
- Deployment: The requested deployment.
764
- """
765
- response = self.client.get(f'{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}')
766
- return Deployment.from_dict_with_inference_key(response.json(), self._inference_key)
767
-
768
- # Function alias
769
- get_deployment = get_deployment_by_name
770
-
771
- def create_deployment(self, deployment: Deployment) -> Deployment:
772
- """Creates a new container deployment.
773
-
774
- Args:
775
- deployment: Deployment configuration to create.
776
-
777
- Returns:
778
- Deployment: The created deployment.
779
- """
780
- response = self.client.post(CONTAINER_DEPLOYMENTS_ENDPOINT, deployment.to_dict())
781
- return Deployment.from_dict_with_inference_key(response.json(), self._inference_key)
782
-
783
- def update_deployment(self, deployment_name: str, deployment: Deployment) -> Deployment:
784
- """Updates an existing deployment.
785
-
786
- Args:
787
- deployment_name: Name of the deployment to update.
788
- deployment: Updated deployment configuration.
789
-
790
- Returns:
791
- Deployment: The updated deployment.
792
- """
793
- response = self.client.patch(
794
- f'{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}', deployment.to_dict()
795
- )
796
- return Deployment.from_dict_with_inference_key(response.json(), self._inference_key)
797
-
798
- def delete_deployment(self, deployment_name: str) -> None:
799
- """Deletes a deployment.
800
-
801
- Args:
802
- deployment_name: Name of the deployment to delete.
803
- """
804
- self.client.delete(f'{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}')
805
-
806
- def get_deployment_status(self, deployment_name: str) -> ContainerDeploymentStatus:
807
- """Retrieves the current status of a deployment.
808
-
809
- Args:
810
- deployment_name: Name of the deployment.
811
-
812
- Returns:
813
- ContainerDeploymentStatus: Current status of the deployment.
814
- """
815
- response = self.client.get(f'{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/status')
816
- return ContainerDeploymentStatus(response.json()['status'])
817
-
818
- def restart_deployment(self, deployment_name: str) -> None:
819
- """Restarts a deployment.
820
-
821
- Args:
822
- deployment_name: Name of the deployment to restart.
823
- """
824
- self.client.post(f'{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/restart')
825
-
826
- def get_deployment_scaling_options(self, deployment_name: str) -> ScalingOptions:
827
- """Retrieves the scaling options for a deployment.
828
-
829
- Args:
830
- deployment_name: Name of the deployment.
831
-
832
- Returns:
833
- ScalingOptions: Current scaling options for the deployment.
834
- """
835
- response = self.client.get(f'{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/scaling')
836
- return ScalingOptions.from_dict(response.json())
837
-
838
- def update_deployment_scaling_options(
839
- self, deployment_name: str, scaling_options: ScalingOptions
840
- ) -> ScalingOptions:
841
- """Updates the scaling options for a deployment.
842
-
843
- Args:
844
- deployment_name: Name of the deployment.
845
- scaling_options: New scaling options to apply.
846
-
847
- Returns:
848
- ScalingOptions: Updated scaling options for the deployment.
849
- """
850
- response = self.client.patch(
851
- f'{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/scaling',
852
- scaling_options.to_dict(),
853
- )
854
- return ScalingOptions.from_dict(response.json())
855
-
856
- def get_deployment_replicas(self, deployment_name: str) -> List[ReplicaInfo]:
857
- """Retrieves information about deployment replicas.
858
-
859
- Args:
860
- deployment_name: Name of the deployment.
861
-
862
- Returns:
863
- List[ReplicaInfo]: List of replica information.
864
- """
865
- response = self.client.get(f'{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/replicas')
866
- return [ReplicaInfo.from_dict(replica) for replica in response.json()['list']]
867
-
868
- def purge_deployment_queue(self, deployment_name: str) -> None:
869
- """Purges the deployment queue.
870
-
871
- Args:
872
- deployment_name: Name of the deployment.
873
- """
874
- self.client.post(f'{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/purge-queue')
875
-
876
- def pause_deployment(self, deployment_name: str) -> None:
877
- """Pauses a deployment.
878
-
879
- Args:
880
- deployment_name: Name of the deployment to pause.
881
- """
882
- self.client.post(f'{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/pause')
883
-
884
- def resume_deployment(self, deployment_name: str) -> None:
885
- """Resumes a paused deployment.
886
-
887
- Args:
888
- deployment_name: Name of the deployment to resume.
889
- """
890
- self.client.post(f'{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/resume')
891
-
892
- def get_deployment_environment_variables(self, deployment_name: str) -> Dict[str, List[EnvVar]]:
893
- """Retrieves environment variables for a deployment.
894
-
895
- Args:
896
- deployment_name: Name of the deployment.
897
-
898
- Returns:
899
- Dict[str, List[EnvVar]]: Dictionary mapping container names to their environment variables.
900
- """
901
- response = self.client.get(
902
- f'{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/environment-variables'
903
- )
904
- result = {}
905
- for item in response.json():
906
- container_name = item['container_name']
907
- env_vars = item['env']
908
- result[container_name] = [EnvVar.from_dict(env_var) for env_var in env_vars]
909
- return result
910
-
911
- def add_deployment_environment_variables(
912
- self, deployment_name: str, container_name: str, env_vars: List[EnvVar]
913
- ) -> Dict[str, List[EnvVar]]:
914
- """Adds environment variables to a container in a deployment.
915
-
916
- Args:
917
- deployment_name: Name of the deployment.
918
- container_name: Name of the container.
919
- env_vars: List of environment variables to add.
920
-
921
- Returns:
922
- Dict[str, List[EnvVar]]: Updated environment variables for all containers.
923
- """
924
- response = self.client.post(
925
- f'{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/environment-variables',
926
- {
927
- 'container_name': container_name,
928
- 'env': [env_var.to_dict() for env_var in env_vars],
929
- },
930
- )
931
- result = {}
932
- for item in response.json():
933
- container_name = item['container_name']
934
- env_vars = item['env']
935
- result[container_name] = [EnvVar.from_dict(env_var) for env_var in env_vars]
936
- return result
937
-
938
- def update_deployment_environment_variables(
939
- self, deployment_name: str, container_name: str, env_vars: List[EnvVar]
940
- ) -> Dict[str, List[EnvVar]]:
941
- """Updates environment variables for a container in a deployment.
942
-
943
- Args:
944
- deployment_name: Name of the deployment.
945
- container_name: Name of the container.
946
- env_vars: List of updated environment variables.
947
-
948
- Returns:
949
- Dict[str, List[EnvVar]]: Updated environment variables for all containers.
950
- """
951
- response = self.client.patch(
952
- f'{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/environment-variables',
953
- {
954
- 'container_name': container_name,
955
- 'env': [env_var.to_dict() for env_var in env_vars],
956
- },
957
- )
958
- result = {}
959
- item = response.json()
960
- container_name = item['container_name']
961
- env_vars = item['env']
962
- result[container_name] = [EnvVar.from_dict(env_var) for env_var in env_vars]
963
- return result
964
-
965
- def delete_deployment_environment_variables(
966
- self, deployment_name: str, container_name: str, env_var_names: List[str]
967
- ) -> Dict[str, List[EnvVar]]:
968
- """Deletes environment variables from a container in a deployment.
969
-
970
- Args:
971
- deployment_name: Name of the deployment.
972
- container_name: Name of the container.
973
- env_var_names: List of environment variable names to delete.
974
-
975
- Returns:
976
- Dict[str, List[EnvVar]]: Updated environment variables for all containers.
977
- """
978
- response = self.client.delete(
979
- f'{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/environment-variables',
980
- {'container_name': container_name, 'env': env_var_names},
981
- )
982
- result = {}
983
- for item in response.json():
984
- container_name = item['container_name']
985
- env_vars = item['env']
986
- result[container_name] = [EnvVar.from_dict(env_var) for env_var in env_vars]
987
- return result
988
-
989
- def get_compute_resources(
990
- self, size: int = None, is_available: bool = None
991
- ) -> List[ComputeResource]:
992
- """Retrieves compute resources, optionally filtered by size and availability.
993
-
994
- Args:
995
- size: Optional size to filter resources by (e.g. 8 for 8x GPUs)
996
- available: Optional boolean to filter by availability status
997
-
998
- Returns:
999
- List[ComputeResource]: List of compute resources matching the filters.
1000
- If no filters provided, returns all resources.
1001
- """
1002
- response = self.client.get(SERVERLESS_COMPUTE_RESOURCES_ENDPOINT)
1003
- resources = []
1004
- for resource_group in response.json():
1005
- for resource in resource_group:
1006
- resources.append(ComputeResource.from_dict(resource))
1007
- if size:
1008
- resources = [r for r in resources if r.size == size]
1009
- if is_available:
1010
- resources = [r for r in resources if r.is_available == is_available]
1011
- return resources
1012
-
1013
- # Function alias
1014
- get_gpus = get_compute_resources
1015
-
1016
- def get_secrets(self) -> List[Secret]:
1017
- """Retrieves all secrets.
1018
-
1019
- Returns:
1020
- List[Secret]: List of all secrets.
1021
- """
1022
- response = self.client.get(SECRETS_ENDPOINT)
1023
- return [Secret.from_dict(secret) for secret in response.json()]
1024
-
1025
- def create_secret(self, name: str, value: str) -> None:
1026
- """Creates a new secret.
1027
-
1028
- Args:
1029
- name: Name of the secret.
1030
- value: Value of the secret.
1031
- """
1032
- self.client.post(SECRETS_ENDPOINT, {'name': name, 'value': value})
1033
-
1034
- def delete_secret(self, secret_name: str, force: bool = False) -> None:
1035
- """Deletes a secret.
1036
-
1037
- Args:
1038
- secret_name: Name of the secret to delete.
1039
- force: Whether to force delete even if secret is in use.
1040
- """
1041
- self.client.delete(
1042
- f'{SECRETS_ENDPOINT}/{secret_name}', params={'force': str(force).lower()}
1043
- )
1044
-
1045
- def get_registry_credentials(self) -> List[RegistryCredential]:
1046
- """Retrieves all registry credentials.
1047
-
1048
- Returns:
1049
- List[RegistryCredential]: List of all registry credentials.
1050
- """
1051
- response = self.client.get(CONTAINER_REGISTRY_CREDENTIALS_ENDPOINT)
1052
- return [RegistryCredential.from_dict(credential) for credential in response.json()]
1053
-
1054
- def add_registry_credentials(self, credentials: BaseRegistryCredentials) -> None:
1055
- """Adds new registry credentials.
1056
-
1057
- Args:
1058
- credentials: Registry credentials to add.
1059
- """
1060
- data = credentials.to_dict()
1061
- self.client.post(CONTAINER_REGISTRY_CREDENTIALS_ENDPOINT, data)
1062
-
1063
- def delete_registry_credentials(self, credentials_name: str) -> None:
1064
- """Deletes registry credentials.
1065
-
1066
- Args:
1067
- credentials_name: Name of the credentials to delete.
1068
- """
1069
- self.client.delete(f'{CONTAINER_REGISTRY_CREDENTIALS_ENDPOINT}/{credentials_name}')
1070
-
1071
- def get_fileset_secrets(self) -> List[Secret]:
1072
- """Retrieves all fileset secrets.
1073
-
1074
- Returns:
1075
- List of all fileset secrets.
1076
- """
1077
- response = self.client.get(FILESET_SECRETS_ENDPOINT)
1078
- return [Secret.from_dict(secret) for secret in response.json()]
1079
-
1080
- def delete_fileset_secret(self, secret_name: str) -> None:
1081
- """Deletes a fileset secret.
1082
-
1083
- Args:
1084
- secret_name: Name of the secret to delete.
1085
- """
1086
- self.client.delete(f'{FILESET_SECRETS_ENDPOINT}/{secret_name}')
1087
-
1088
- def create_fileset_secret_from_file_paths(
1089
- self, secret_name: str, file_paths: List[str]
1090
- ) -> None:
1091
- """Creates a new fileset secret.
1092
- A fileset secret is a secret that contains several files,
1093
- and can be used to mount a directory with the files in a container.
1094
-
1095
- Args:
1096
- secret_name: Name of the secret.
1097
- file_paths: List of file paths to include in the secret.
1098
- """
1099
- processed_files = []
1100
- for file_path in file_paths:
1101
- with open(file_path, 'rb') as f:
1102
- base64_content = base64.b64encode(f.read()).decode('utf-8')
1103
- processed_files.append(
1104
- {
1105
- 'file_name': os.path.basename(file_path),
1106
- 'base64_content': base64_content,
1107
- }
1108
- )
1109
- self.client.post(FILESET_SECRETS_ENDPOINT, {'name': secret_name, 'files': processed_files})