datacrunch 1.15.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 (72) hide show
  1. datacrunch/__init__.py +53 -1
  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.17.1.dist-info/WHEEL +4 -0
  6. datacrunch/InferenceClient/__init__.py +0 -3
  7. datacrunch/InferenceClient/inference_client.py +0 -379
  8. datacrunch/__version__.py +0 -1
  9. datacrunch/authentication/__init__.py +0 -0
  10. datacrunch/authentication/authentication.py +0 -112
  11. datacrunch/balance/__init__.py +0 -0
  12. datacrunch/balance/balance.py +0 -52
  13. datacrunch/constants.py +0 -107
  14. datacrunch/containers/__init__.py +0 -33
  15. datacrunch/containers/containers.py +0 -1081
  16. datacrunch/exceptions.py +0 -29
  17. datacrunch/helpers.py +0 -13
  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 -87
  22. datacrunch/instance_types/__init__.py +0 -0
  23. datacrunch/instance_types/instance_types.py +0 -188
  24. datacrunch/instances/__init__.py +0 -0
  25. datacrunch/instances/instances.py +0 -247
  26. datacrunch/locations/__init__.py +0 -0
  27. datacrunch/locations/locations.py +0 -16
  28. datacrunch/ssh_keys/__init__.py +0 -0
  29. datacrunch/ssh_keys/ssh_keys.py +0 -112
  30. datacrunch/startup_scripts/__init__.py +0 -0
  31. datacrunch/startup_scripts/startup_scripts.py +0 -113
  32. datacrunch/volume_types/__init__.py +0 -0
  33. datacrunch/volume_types/volume_types.py +0 -66
  34. datacrunch/volumes/__init__.py +0 -0
  35. datacrunch/volumes/volumes.py +0 -398
  36. datacrunch-1.15.0.dist-info/METADATA +0 -208
  37. datacrunch-1.15.0.dist-info/RECORD +0 -69
  38. datacrunch-1.15.0.dist-info/WHEEL +0 -5
  39. datacrunch-1.15.0.dist-info/licenses/LICENSE +0 -21
  40. datacrunch-1.15.0.dist-info/top_level.txt +0 -2
  41. tests/__init__.py +0 -0
  42. tests/integration_tests/__init__.py +0 -0
  43. tests/integration_tests/conftest.py +0 -20
  44. tests/integration_tests/test_instances.py +0 -36
  45. tests/integration_tests/test_locations.py +0 -65
  46. tests/integration_tests/test_volumes.py +0 -94
  47. tests/unit_tests/__init__.py +0 -0
  48. tests/unit_tests/authentication/__init__.py +0 -0
  49. tests/unit_tests/authentication/test_authentication.py +0 -202
  50. tests/unit_tests/balance/__init__.py +0 -0
  51. tests/unit_tests/balance/test_balance.py +0 -25
  52. tests/unit_tests/conftest.py +0 -21
  53. tests/unit_tests/containers/__init__.py +0 -1
  54. tests/unit_tests/containers/test_containers.py +0 -959
  55. tests/unit_tests/http_client/__init__.py +0 -0
  56. tests/unit_tests/http_client/test_http_client.py +0 -193
  57. tests/unit_tests/images/__init__.py +0 -0
  58. tests/unit_tests/images/test_images.py +0 -41
  59. tests/unit_tests/instance_types/__init__.py +0 -0
  60. tests/unit_tests/instance_types/test_instance_types.py +0 -87
  61. tests/unit_tests/instances/__init__.py +0 -0
  62. tests/unit_tests/instances/test_instances.py +0 -483
  63. tests/unit_tests/ssh_keys/__init__.py +0 -0
  64. tests/unit_tests/ssh_keys/test_ssh_keys.py +0 -198
  65. tests/unit_tests/startup_scripts/__init__.py +0 -0
  66. tests/unit_tests/startup_scripts/test_startup_scripts.py +0 -196
  67. tests/unit_tests/test_datacrunch.py +0 -65
  68. tests/unit_tests/test_exceptions.py +0 -33
  69. tests/unit_tests/volume_types/__init__.py +0 -0
  70. tests/unit_tests/volume_types/test_volume_types.py +0 -50
  71. tests/unit_tests/volumes/__init__.py +0 -0
  72. tests/unit_tests/volumes/test_volumes.py +0 -641
@@ -1,247 +0,0 @@
1
- import time
2
- import itertools
3
- from typing import List, Union, Optional, Dict, Literal
4
- from dataclasses import dataclass
5
- from dataclasses_json import dataclass_json
6
- from datacrunch.constants import Locations, InstanceStatus
7
-
8
- INSTANCES_ENDPOINT = '/instances'
9
-
10
- Contract = Literal['LONG_TERM', 'PAY_AS_YOU_GO', 'SPOT']
11
- Pricing = Literal['DYNAMIC_PRICE', 'FIXED_PRICE']
12
-
13
-
14
- @dataclass_json
15
- @dataclass
16
- class Instance:
17
- """Represents a cloud instance with its configuration and state.
18
-
19
- Attributes:
20
- id: Unique identifier for the instance.
21
- instance_type: Type of the instance (e.g., '8V100.48V').
22
- price_per_hour: Cost per hour of running the instance.
23
- hostname: Network hostname of the instance.
24
- description: Human-readable description of the instance.
25
- status: Current operational status of the instance.
26
- created_at: Timestamp of instance creation.
27
- ssh_key_ids: List of SSH key IDs associated with the instance.
28
- cpu: CPU configuration details.
29
- gpu: GPU configuration details.
30
- memory: Memory configuration details.
31
- storage: Storage configuration details.
32
- gpu_memory: GPU memory configuration details.
33
- ip: IP address assigned to the instance.
34
- os_volume_id: ID of the operating system volume.
35
- location: Datacenter location code (default: Locations.FIN_01).
36
- image: Image ID or type used for the instance.
37
- startup_script_id: ID of the startup script to run.
38
- is_spot: Whether the instance is a spot instance.
39
- contract: Contract type for the instance. (e.g. 'LONG_TERM', 'PAY_AS_YOU_GO', 'SPOT')
40
- pricing: Pricing model for the instance. (e.g. 'DYNAMIC_PRICE', 'FIXED_PRICE')
41
- """
42
-
43
- id: str
44
- instance_type: str
45
- price_per_hour: float
46
- hostname: str
47
- description: str
48
- status: str
49
- created_at: str
50
- ssh_key_ids: List[str]
51
- cpu: dict
52
- gpu: dict
53
- memory: dict
54
- storage: dict
55
- gpu_memory: dict
56
- # Can be None if instance is still not provisioned
57
- ip: Optional[str] = None
58
- # Can be None if instance is still not provisioned
59
- os_volume_id: Optional[str] = None
60
- location: str = Locations.FIN_01
61
- image: Optional[str] = None
62
- startup_script_id: Optional[str] = None
63
- is_spot: bool = False
64
- contract: Optional[Contract] = None
65
- pricing: Optional[Pricing] = None
66
-
67
-
68
- class InstancesService:
69
- """Service for managing cloud instances through the API.
70
-
71
- This service provides methods to create, retrieve, and manage cloud instances
72
- through the DataCrunch API.
73
- """
74
-
75
- def __init__(self, http_client) -> None:
76
- """Initializes the InstancesService with an HTTP client.
77
-
78
- Args:
79
- http_client: HTTP client for making API requests.
80
- """
81
- self._http_client = http_client
82
-
83
- def get(self, status: Optional[str] = None) -> List[Instance]:
84
- """Retrieves all non-deleted instances or instances with specific status.
85
-
86
- Args:
87
- status: Optional status filter for instances. If None, returns all
88
- non-deleted instances.
89
-
90
- Returns:
91
- List of instance objects matching the criteria.
92
- """
93
- instances_dict = self._http_client.get(
94
- INSTANCES_ENDPOINT, params={'status': status}).json()
95
- return [Instance.from_dict(instance_dict, infer_missing=True) for instance_dict in instances_dict]
96
-
97
- def get_by_id(self, id: str) -> Instance:
98
- """Retrieves a specific instance by its ID.
99
-
100
- Args:
101
- id: Unique identifier of the instance to retrieve.
102
-
103
- Returns:
104
- Instance object with the specified ID.
105
-
106
- Raises:
107
- HTTPError: If the instance is not found or other API error occurs.
108
- """
109
- instance_dict = self._http_client.get(
110
- INSTANCES_ENDPOINT + f'/{id}').json()
111
- return Instance.from_dict(instance_dict, infer_missing=True)
112
-
113
- def create(self,
114
- instance_type: str,
115
- image: str,
116
- hostname: str,
117
- description: str,
118
- ssh_key_ids: list = [],
119
- location: str = Locations.FIN_01,
120
- startup_script_id: Optional[str] = None,
121
- volumes: Optional[List[Dict]] = None,
122
- existing_volumes: Optional[List[str]] = None,
123
- os_volume: Optional[Dict] = None,
124
- is_spot: bool = False,
125
- contract: Optional[Contract] = None,
126
- pricing: Optional[Pricing] = None,
127
- coupon: Optional[str] = None,
128
- *,
129
- max_wait_time: float = 180,
130
- initial_interval: float = 0.5,
131
- max_interval: float = 5,
132
- backoff_coefficient: float = 2.0) -> Instance:
133
- """Creates and deploys a new cloud instance.
134
-
135
- Args:
136
- instance_type: Type of instance to create (e.g., '8V100.48V').
137
- image: Image type or existing OS volume ID for the instance.
138
- hostname: Network hostname for the instance.
139
- description: Human-readable description of the instance.
140
- ssh_key_ids: List of SSH key IDs to associate with the instance.
141
- location: Datacenter location code (default: Locations.FIN_01).
142
- startup_script_id: Optional ID of startup script to run.
143
- volumes: Optional list of volume configurations to create.
144
- existing_volumes: Optional list of existing volume IDs to attach.
145
- os_volume: Optional OS volume configuration details.
146
- is_spot: Whether to create a spot instance.
147
- contract: Optional contract type for the instance.
148
- pricing: Optional pricing model for the instance.
149
- coupon: Optional coupon code for discounts.
150
- max_wait_time: Maximum total wait for the instance to start provisioning, in seconds (default: 180)
151
- initial_interval: Initial interval, in seconds (default: 0.5)
152
- max_interval: The longest single delay allowed between retries, in seconds (default: 5)
153
- backoff_coefficient: Coefficient to calculate the next retry interval (default 2.0)
154
-
155
- Returns:
156
- The newly created instance object.
157
-
158
- Raises:
159
- HTTPError: If instance creation fails or other API error occurs.
160
- """
161
- payload = {
162
- "instance_type": instance_type,
163
- "image": image,
164
- "ssh_key_ids": ssh_key_ids,
165
- "startup_script_id": startup_script_id,
166
- "hostname": hostname,
167
- "description": description,
168
- "location_code": location,
169
- "os_volume": os_volume,
170
- "volumes": volumes,
171
- "existing_volumes": existing_volumes,
172
- "is_spot": is_spot,
173
- "coupon": coupon,
174
- }
175
- if contract:
176
- payload['contract'] = contract
177
- if pricing:
178
- payload['pricing'] = pricing
179
- id = self._http_client.post(INSTANCES_ENDPOINT, json=payload).text
180
-
181
- # Wait for instance to enter provisioning state with timeout
182
- deadline = time.monotonic() + max_wait_time
183
- for i in itertools.count():
184
- instance = self.get_by_id(id)
185
- if instance.status != InstanceStatus.ORDERED:
186
- return instance
187
-
188
- now = time.monotonic()
189
- if now >= deadline:
190
- raise TimeoutError(
191
- f"Instance {id} did not enter provisioning state within {max_wait_time:.1f} seconds")
192
-
193
- interval = min(initial_interval * backoff_coefficient ** i, max_interval, deadline - now)
194
- time.sleep(interval)
195
-
196
- def action(self, id_list: Union[List[str], str], action: str, volume_ids: Optional[List[str]] = None) -> None:
197
- """Performs an action on one or more instances.
198
-
199
- Args:
200
- id_list: Single instance ID or list of instance IDs to act upon.
201
- action: Action to perform on the instances.
202
- volume_ids: Optional list of volume IDs to delete.
203
-
204
- Raises:
205
- HTTPError: If the action fails or other API error occurs.
206
- """
207
- if type(id_list) is str:
208
- id_list = [id_list]
209
-
210
- payload = {
211
- "id": id_list,
212
- "action": action,
213
- "volume_ids": volume_ids
214
- }
215
-
216
- self._http_client.put(INSTANCES_ENDPOINT, json=payload)
217
- return
218
-
219
- def is_available(self, instance_type: str, is_spot: bool = False, location_code: Optional[str] = None) -> bool:
220
- """Checks if a specific instance type is available for deployment.
221
-
222
- Args:
223
- instance_type: Type of instance to check availability for.
224
- is_spot: Whether to check spot instance availability.
225
- location_code: Optional datacenter location code.
226
-
227
- Returns:
228
- True if the instance type is available, False otherwise.
229
- """
230
- is_spot = str(is_spot).lower()
231
- query_params = {'isSpot': is_spot, 'location_code': location_code}
232
- url = f'/instance-availability/{instance_type}'
233
- return self._http_client.get(url, query_params).json()
234
-
235
- def get_availabilities(self, is_spot: Optional[bool] = None, location_code: Optional[str] = None) -> List[Dict]:
236
- """Retrieves a list of available instance types across locations.
237
-
238
- Args:
239
- is_spot: Optional flag to filter spot instance availability.
240
- location_code: Optional datacenter location code to filter by.
241
-
242
- Returns:
243
- List of available instance types and their details.
244
- """
245
- is_spot = str(is_spot).lower() if is_spot is not None else None
246
- query_params = {'isSpot': is_spot, 'locationCode': location_code}
247
- return self._http_client.get('/instance-availability', params=query_params).json()
File without changes
@@ -1,16 +0,0 @@
1
- from typing import List
2
-
3
- LOCATIONS_ENDPOINT = '/locations'
4
-
5
-
6
- class LocationsService:
7
- """A service for interacting with the locations endpoint"""
8
-
9
- def __init__(self, http_client) -> None:
10
- self._http_client = http_client
11
-
12
- def get(self) -> List[dict]:
13
- """Get all locations
14
- """
15
- locations = self._http_client.get(LOCATIONS_ENDPOINT).json()
16
- return locations
File without changes
@@ -1,112 +0,0 @@
1
- from typing import List
2
-
3
- SSHKEYS_ENDPOINT = '/sshkeys'
4
-
5
-
6
- class SSHKey:
7
- """An SSH key model class"""
8
-
9
- def __init__(self, id: str, name: str, public_key: str) -> None:
10
- """Initialize a new SSH key object
11
-
12
- :param id: SSH key id
13
- :type id: str
14
- :param name: SSH key name
15
- :type name: str
16
- :param public_key: SSH key public key
17
- :type public_key: str
18
- """
19
- self._id = id
20
- self._name = name
21
- self._public_key = public_key
22
-
23
- @property
24
- def id(self) -> str:
25
- """Get the SSH key id
26
-
27
- :return: SSH key id
28
- :rtype: str
29
- """
30
- return self._id
31
-
32
- @property
33
- def name(self) -> str:
34
- """Get the SSH key name
35
-
36
- :return: SSH key name
37
- :rtype: str
38
- """
39
- return self._name
40
-
41
- @property
42
- def public_key(self) -> str:
43
- """Get the SSH key public key value
44
-
45
- :return: public SSH key
46
- :rtype: str
47
- """
48
- return self._public_key
49
-
50
-
51
- class SSHKeysService:
52
- """A service for interacting with the SSH keys endpoint"""
53
-
54
- def __init__(self, http_client) -> None:
55
- self._http_client = http_client
56
-
57
- def get(self) -> List[SSHKey]:
58
- """Get all of the client's SSH keys
59
-
60
- :return: list of SSH keys objects
61
- :rtype: List[SSHKey]
62
- """
63
- keys = self._http_client.get(SSHKEYS_ENDPOINT).json()
64
- keys_object_list = list(map(lambda key: SSHKey(
65
- key['id'], key['name'], key['key']), keys))
66
-
67
- return keys_object_list
68
-
69
- def get_by_id(self, id: str) -> SSHKey:
70
- """Get a specific SSH key by id.
71
-
72
- :param id: SSH key id
73
- :type id: str
74
- :return: SSHKey object
75
- :rtype: SSHKey
76
- """
77
- key_dict = self._http_client.get(SSHKEYS_ENDPOINT + f'/{id}').json()[0]
78
- key_object = SSHKey(key_dict['id'], key_dict['name'], key_dict['key'])
79
- return key_object
80
-
81
- def delete(self, id_list: List[str]) -> None:
82
- """Delete multiple SSH keys by id
83
-
84
- :param id_list: list of SSH keys ids
85
- :type id_list: List[str]
86
- """
87
- payload = {"keys": id_list}
88
- self._http_client.delete(SSHKEYS_ENDPOINT, json=payload)
89
- return
90
-
91
- def delete_by_id(self, id: str) -> None:
92
- """Delete a single SSH key by id
93
-
94
- :param id: SSH key id
95
- :type id: str
96
- """
97
- self._http_client.delete(SSHKEYS_ENDPOINT + f'/{id}')
98
- return
99
-
100
- def create(self, name: str, key: str) -> SSHKey:
101
- """Create a new SSH key
102
-
103
- :param name: SSH key name
104
- :type name: str
105
- :param key: public SSH key value
106
- :type key: str
107
- :return: new SSH key object
108
- :rtype: SSHKey
109
- """
110
- payload = {"name": name, "key": key}
111
- id = self._http_client.post(SSHKEYS_ENDPOINT, json=payload).text
112
- return SSHKey(id, name, key)
File without changes
@@ -1,113 +0,0 @@
1
- from typing import List
2
-
3
- STARTUP_SCRIPTS_ENDPOINT = '/scripts'
4
-
5
-
6
- class StartupScript:
7
- """A startup script model class"""
8
-
9
- def __init__(self, id: str, name: str, script: str) -> None:
10
- """Initialize a new startup script object
11
-
12
- :param id: startup script id
13
- :type id: str
14
- :param name: startup script name
15
- :type name: str
16
- :param script: the actual script
17
- :type script: str
18
- """
19
- self._id = id
20
- self._name = name
21
- self._script = script
22
-
23
- @property
24
- def id(self) -> str:
25
- """Get the startup script id
26
-
27
- :return: startup script id
28
- :rtype: str
29
- """
30
- return self._id
31
-
32
- @property
33
- def name(self) -> str:
34
- """Get the startup script name
35
-
36
- :return: startup script name
37
- :rtype: str
38
- """
39
- return self._name
40
-
41
- @property
42
- def script(self) -> str:
43
- """Get the actual startup script code
44
-
45
- :return: startup script text
46
- :rtype: str
47
- """
48
- return self._script
49
-
50
-
51
- class StartupScriptsService:
52
- """A service for interacting with the startup scripts endpoint"""
53
-
54
- def __init__(self, http_client) -> None:
55
- self._http_client = http_client
56
-
57
- def get(self) -> List[StartupScript]:
58
- """Get all of the client's startup scripts
59
-
60
- :return: list of startup script objects
61
- :rtype: List[StartupScript]
62
- """
63
- scripts = self._http_client.get(STARTUP_SCRIPTS_ENDPOINT).json()
64
- scripts_objects = list(map(lambda script: StartupScript(
65
- script['id'], script['name'], script['script']), scripts))
66
- return scripts_objects
67
-
68
- def get_by_id(self, id) -> StartupScript:
69
- """Get a specific startup script by id.
70
-
71
- :param id: startup script id
72
- :type id: str
73
- :return: startup script object
74
- :rtype: StartupScript
75
- """
76
- script = self._http_client.get(
77
- STARTUP_SCRIPTS_ENDPOINT + f'/{id}').json()[0]
78
-
79
- return StartupScript(script['id'], script['name'], script['script'])
80
-
81
- def delete(self, id_list: List[str]) -> None:
82
- """Delete multiple startup scripts by id
83
-
84
- :param id_list: list of startup scripts ids
85
- :type id_list: List[str]
86
- """
87
- payload = {"scripts": id_list}
88
- self._http_client.delete(STARTUP_SCRIPTS_ENDPOINT, json=payload)
89
- return
90
-
91
- def delete_by_id(self, id: str) -> None:
92
- """Delete a single startup script by id
93
-
94
- :param id: startup script id
95
- :type id: str
96
- """
97
- self._http_client.delete(STARTUP_SCRIPTS_ENDPOINT + f'/{id}')
98
- return
99
-
100
- def create(self, name: str, script: str) -> StartupScript:
101
- """Create a new startup script
102
-
103
- :param name: startup script name
104
- :type name: str
105
- :param script: startup script value
106
- :type script: str
107
- :return: the new startup script's id
108
- :rtype: str
109
- """
110
- payload = {"name": name, "script": script}
111
- id = self._http_client.post(
112
- STARTUP_SCRIPTS_ENDPOINT, json=payload).text
113
- return StartupScript(id, name, script)
File without changes
@@ -1,66 +0,0 @@
1
- from typing import List
2
-
3
- VOLUME_TYPES_ENDPOINT = '/volume-types'
4
-
5
-
6
- class VolumeType:
7
-
8
- def __init__(self,
9
- type: str,
10
- price_per_month_per_gb: float) -> None:
11
- """Initialize a volume type object
12
-
13
- :param type: volume type name
14
- :type type: str
15
- :param price_per_month_per_gb: price per month per gb of storage
16
- :type price_per_month_per_gb: float
17
- """
18
- self._type = type
19
- self._price_per_month_per_gb = price_per_month_per_gb
20
-
21
- @property
22
- def type(self) -> str:
23
- """Get the volume type
24
-
25
- :return: volume type
26
- :rtype: str
27
- """
28
- return self._type
29
-
30
- @property
31
- def price_per_month_per_gb(self) -> str:
32
- """Get the volume price_per_month_per_gb
33
-
34
- :return: volume price_per_month_per_gb
35
- :rtype: str
36
- """
37
- return self._price_per_month_per_gb
38
-
39
- def __str__(self) -> str:
40
- """Prints the volume type
41
-
42
- :return: volume type string representation
43
- :rtype: str
44
- """
45
- return f'type: {self._type}\nprice_per_month_per_gb: ${self._price_per_month_per_gb}'
46
-
47
-
48
- class VolumeTypesService:
49
- """A service for interacting with the volume-types endpoint"""
50
-
51
- def __init__(self, http_client) -> None:
52
- self._http_client = http_client
53
-
54
- def get(self) -> List[VolumeType]:
55
- """Get all volume types
56
-
57
- :return: list of volume type objects
58
- :rtype: List[VolumesType]
59
- """
60
- volume_types = self._http_client.get(VOLUME_TYPES_ENDPOINT).json()
61
- volume_type_objects = list(map(lambda volume_type: VolumeType(
62
- type=volume_type['type'],
63
- price_per_month_per_gb=volume_type['price']['price_per_month_per_gb'],
64
- ), volume_types))
65
-
66
- return volume_type_objects
File without changes