proximl 0.5.16__py3-none-any.whl → 1.0.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.
Files changed (56) hide show
  1. examples/local_storage.py +0 -2
  2. proximl/__init__.py +1 -1
  3. proximl/checkpoints.py +56 -57
  4. proximl/cli/__init__.py +6 -3
  5. proximl/cli/checkpoint.py +18 -57
  6. proximl/cli/dataset.py +17 -57
  7. proximl/cli/job/__init__.py +11 -53
  8. proximl/cli/job/create.py +51 -24
  9. proximl/cli/model.py +14 -56
  10. proximl/cli/volume.py +18 -57
  11. proximl/datasets.py +50 -55
  12. proximl/jobs.py +239 -68
  13. proximl/models.py +51 -55
  14. proximl/projects/projects.py +2 -2
  15. proximl/proximl.py +50 -16
  16. proximl/utils/__init__.py +1 -0
  17. proximl/{auth.py → utils/auth.py} +4 -3
  18. proximl/utils/transfer.py +587 -0
  19. proximl/volumes.py +48 -53
  20. {proximl-0.5.16.dist-info → proximl-1.0.0.dist-info}/METADATA +3 -3
  21. {proximl-0.5.16.dist-info → proximl-1.0.0.dist-info}/RECORD +53 -51
  22. tests/integration/test_checkpoints_integration.py +4 -3
  23. tests/integration/test_datasets_integration.py +5 -3
  24. tests/integration/test_jobs_integration.py +33 -27
  25. tests/integration/test_models_integration.py +7 -3
  26. tests/integration/test_volumes_integration.py +2 -2
  27. tests/unit/cli/test_cli_checkpoint_unit.py +312 -1
  28. tests/unit/cloudbender/test_nodes_unit.py +112 -0
  29. tests/unit/cloudbender/test_providers_unit.py +96 -0
  30. tests/unit/cloudbender/test_regions_unit.py +106 -0
  31. tests/unit/cloudbender/test_services_unit.py +141 -0
  32. tests/unit/conftest.py +23 -10
  33. tests/unit/projects/test_project_data_connectors_unit.py +39 -0
  34. tests/unit/projects/test_project_datastores_unit.py +37 -0
  35. tests/unit/projects/test_project_members_unit.py +46 -0
  36. tests/unit/projects/test_project_services_unit.py +65 -0
  37. tests/unit/projects/test_projects_unit.py +17 -1
  38. tests/unit/test_auth_unit.py +17 -2
  39. tests/unit/test_checkpoints_unit.py +256 -71
  40. tests/unit/test_datasets_unit.py +218 -68
  41. tests/unit/test_exceptions.py +133 -0
  42. tests/unit/test_gpu_types_unit.py +11 -1
  43. tests/unit/test_jobs_unit.py +1014 -95
  44. tests/unit/test_main_unit.py +20 -0
  45. tests/unit/test_models_unit.py +218 -70
  46. tests/unit/test_proximl_unit.py +627 -3
  47. tests/unit/test_volumes_unit.py +211 -70
  48. tests/unit/utils/__init__.py +1 -0
  49. tests/unit/utils/test_transfer_unit.py +4260 -0
  50. proximl/cli/connection.py +0 -61
  51. proximl/connections.py +0 -621
  52. tests/unit/test_connections_unit.py +0 -182
  53. {proximl-0.5.16.dist-info → proximl-1.0.0.dist-info}/LICENSE +0 -0
  54. {proximl-0.5.16.dist-info → proximl-1.0.0.dist-info}/WHEEL +0 -0
  55. {proximl-0.5.16.dist-info → proximl-1.0.0.dist-info}/entry_points.txt +0 -0
  56. {proximl-0.5.16.dist-info → proximl-1.0.0.dist-info}/top_level.txt +0 -0
@@ -44,7 +44,9 @@ class CheckpointsTests:
44
44
  api_response = dict()
45
45
  mock_proximl._query = AsyncMock(return_value=api_response)
46
46
  await checkpoints.get("1234")
47
- mock_proximl._query.assert_called_once_with("/checkpoint/1234", "GET", dict())
47
+ mock_proximl._query.assert_called_once_with(
48
+ "/checkpoint/1234", "GET", dict()
49
+ )
48
50
 
49
51
  @mark.asyncio
50
52
  async def test_list_checkpoints(
@@ -55,7 +57,30 @@ class CheckpointsTests:
55
57
  api_response = dict()
56
58
  mock_proximl._query = AsyncMock(return_value=api_response)
57
59
  await checkpoints.list()
58
- mock_proximl._query.assert_called_once_with("/checkpoint", "GET", dict())
60
+ mock_proximl._query.assert_called_once_with(
61
+ "/checkpoint", "GET", dict()
62
+ )
63
+
64
+ @mark.asyncio
65
+ async def test_list_public_checkpoints(
66
+ self,
67
+ checkpoints,
68
+ mock_proximl,
69
+ ):
70
+ api_response = [
71
+ dict(
72
+ checkpoint_uuid="1",
73
+ name="public checkpoint",
74
+ status="ready",
75
+ )
76
+ ]
77
+ mock_proximl._query = AsyncMock(return_value=api_response)
78
+ result = await checkpoints.list_public()
79
+ mock_proximl._query.assert_called_once_with(
80
+ "/checkpoint/public", "GET", dict()
81
+ )
82
+ assert len(result) == 1
83
+ assert isinstance(result[0], specimen.Checkpoint)
59
84
 
60
85
  @mark.asyncio
61
86
  async def test_remove_checkpoint(
@@ -133,9 +158,7 @@ class CheckpointTests:
133
158
 
134
159
  @mark.asyncio
135
160
  async def test_checkpoint_get_log_url(self, checkpoint, mock_proximl):
136
- api_response = (
137
- "https://trainml-jobs-dev.s3.us-east-2.amazonaws.com/1/logs/first_one.zip"
138
- )
161
+ api_response = "https://trainml-jobs-dev.s3.us-east-2.amazonaws.com/1/logs/first_one.zip"
139
162
  mock_proximl._query = AsyncMock(return_value=api_response)
140
163
  response = await checkpoint.get_log_url()
141
164
  mock_proximl._query.assert_called_once_with(
@@ -160,81 +183,155 @@ class CheckpointTests:
160
183
  assert response == api_response
161
184
 
162
185
  @mark.asyncio
163
- async def test_checkpoint_get_connection_utility_url(
164
- self, checkpoint, mock_proximl
165
- ):
166
- api_response = (
167
- "https://trainml-jobs-dev.s3.us-east-2.amazonaws.com/1/vpn/first_one.zip"
186
+ async def test_checkpoint_connect_downloading_status(self, mock_proximl):
187
+ checkpoint = specimen.Checkpoint(
188
+ mock_proximl,
189
+ checkpoint_uuid="1",
190
+ project_uuid="proj-id-1",
191
+ name="test checkpoint",
192
+ status="downloading",
193
+ auth_token="test-token",
194
+ hostname="example.com",
195
+ source_uri="/path/to/source",
168
196
  )
169
- mock_proximl._query = AsyncMock(return_value=api_response)
170
- response = await checkpoint.get_connection_utility_url()
171
- mock_proximl._query.assert_called_once_with(
172
- "/checkpoint/1/download", "GET", dict(project_uuid="proj-id-1")
197
+
198
+ with patch(
199
+ "proximl.checkpoints.Checkpoint.refresh", new_callable=AsyncMock
200
+ ) as mock_refresh:
201
+ with patch(
202
+ "proximl.checkpoints.upload", new_callable=AsyncMock
203
+ ) as mock_upload:
204
+ await checkpoint.connect()
205
+ mock_refresh.assert_called_once()
206
+ mock_upload.assert_called_once_with(
207
+ "example.com", "test-token", "/path/to/source"
208
+ )
209
+
210
+ @mark.asyncio
211
+ async def test_checkpoint_connect_exporting_status(
212
+ self, mock_proximl, tmp_path
213
+ ):
214
+ output_dir = str(tmp_path / "output")
215
+ checkpoint = specimen.Checkpoint(
216
+ mock_proximl,
217
+ checkpoint_uuid="1",
218
+ project_uuid="proj-id-1",
219
+ name="test checkpoint",
220
+ status="exporting",
221
+ auth_token="test-token",
222
+ hostname="example.com",
223
+ output_uri=output_dir,
173
224
  )
174
- assert response == api_response
175
225
 
176
- def test_checkpoint_get_connection_details_no_vpn(self, checkpoint):
177
- details = checkpoint.get_connection_details()
178
- expected_details = dict()
179
- assert details == expected_details
226
+ with patch(
227
+ "proximl.checkpoints.Checkpoint.refresh", new_callable=AsyncMock
228
+ ) as mock_refresh:
229
+ with patch(
230
+ "proximl.checkpoints.download", new_callable=AsyncMock
231
+ ) as mock_download:
232
+ await checkpoint.connect()
233
+ mock_refresh.assert_called_once()
234
+ mock_download.assert_called_once_with(
235
+ "example.com", "test-token", output_dir
236
+ )
180
237
 
181
- def test_checkpoint_get_connection_details_local_data(self, mock_proximl):
238
+ @mark.asyncio
239
+ async def test_checkpoint_connect_new_status_waits_for_downloading(
240
+ self, mock_proximl
241
+ ):
182
242
  checkpoint = specimen.Checkpoint(
183
243
  mock_proximl,
184
244
  checkpoint_uuid="1",
185
- project_uuid="a",
186
- name="first one",
245
+ project_uuid="proj-id-1",
246
+ name="test checkpoint",
187
247
  status="new",
188
- size=100000,
189
- createdAt="2020-12-31T23:59:59.000Z",
190
- source_type="local",
191
- source_uri="~/tensorflow-example",
192
- vpn={
193
- "status": "new",
194
- "cidr": "10.106.171.0/24",
195
- "client": {
196
- "port": "36017",
197
- "id": "cus-id-1",
198
- "address": "10.106.171.253",
199
- "ssh_port": 46600,
200
- },
201
- "net_prefix_type_id": 1,
202
- },
203
- )
204
- details = checkpoint.get_connection_details()
205
- expected_details = dict(
206
- project_uuid="a",
207
- entity_type="checkpoint",
208
- cidr="10.106.171.0/24",
209
- ssh_port=46600,
210
- input_path="~/tensorflow-example",
211
- output_path=None,
212
- )
213
- assert details == expected_details
248
+ )
249
+
250
+ with patch(
251
+ "proximl.checkpoints.Checkpoint.wait_for", new_callable=AsyncMock
252
+ ) as mock_wait:
253
+ with patch(
254
+ "proximl.checkpoints.Checkpoint.refresh",
255
+ new_callable=AsyncMock,
256
+ ) as mock_refresh:
257
+ # After refresh, status becomes downloading
258
+ def update_status():
259
+ checkpoint._status = "downloading"
260
+ checkpoint._checkpoint.update(
261
+ {
262
+ "auth_token": "test-token",
263
+ "hostname": "example.com",
264
+ "source_uri": "/path/to/source",
265
+ }
266
+ )
267
+
268
+ mock_refresh.side_effect = update_status
269
+
270
+ with patch(
271
+ "proximl.checkpoints.upload", new_callable=AsyncMock
272
+ ) as mock_upload:
273
+ await checkpoint.connect()
274
+ mock_wait.assert_called_once_with("downloading")
275
+ mock_refresh.assert_called_once()
276
+ mock_upload.assert_called_once()
214
277
 
215
278
  @mark.asyncio
216
- async def test_checkpoint_connect(self, checkpoint, mock_proximl):
279
+ async def test_checkpoint_connect_invalid_status(self, mock_proximl):
280
+ checkpoint = specimen.Checkpoint(
281
+ mock_proximl,
282
+ checkpoint_uuid="1",
283
+ project_uuid="proj-id-1",
284
+ name="test checkpoint",
285
+ status="ready",
286
+ )
287
+
288
+ with raises(
289
+ SpecificationError,
290
+ match="You can only connect to downloading or exporting checkpoints",
291
+ ):
292
+ await checkpoint.connect()
293
+
294
+ @mark.asyncio
295
+ async def test_checkpoint_connect_missing_properties_downloading(
296
+ self, mock_proximl
297
+ ):
298
+ checkpoint = specimen.Checkpoint(
299
+ mock_proximl,
300
+ checkpoint_uuid="1",
301
+ project_uuid="proj-id-1",
302
+ name="test checkpoint",
303
+ status="downloading",
304
+ )
305
+
217
306
  with patch(
218
- "proximl.checkpoints.Connection",
219
- autospec=True,
220
- ) as mock_connection:
221
- connection = mock_connection.return_value
222
- connection.status = "connected"
223
- resp = await checkpoint.connect()
224
- connection.start.assert_called_once()
225
- assert resp == "connected"
307
+ "proximl.checkpoints.Checkpoint.refresh", new_callable=AsyncMock
308
+ ):
309
+ with raises(
310
+ SpecificationError,
311
+ match="missing required connection properties",
312
+ ):
313
+ await checkpoint.connect()
226
314
 
227
315
  @mark.asyncio
228
- async def test_checkpoint_disconnect(self, checkpoint, mock_proximl):
316
+ async def test_checkpoint_connect_missing_properties_exporting(
317
+ self, mock_proximl
318
+ ):
319
+ checkpoint = specimen.Checkpoint(
320
+ mock_proximl,
321
+ checkpoint_uuid="1",
322
+ project_uuid="proj-id-1",
323
+ name="test checkpoint",
324
+ status="exporting",
325
+ )
326
+
229
327
  with patch(
230
- "proximl.checkpoints.Connection",
231
- autospec=True,
232
- ) as mock_connection:
233
- connection = mock_connection.return_value
234
- connection.status = "removed"
235
- resp = await checkpoint.disconnect()
236
- connection.stop.assert_called_once()
237
- assert resp == "removed"
328
+ "proximl.checkpoints.Checkpoint.refresh", new_callable=AsyncMock
329
+ ):
330
+ with raises(
331
+ SpecificationError,
332
+ match="missing required connection properties",
333
+ ):
334
+ await checkpoint.connect()
238
335
 
239
336
  @mark.asyncio
240
337
  async def test_checkpoint_remove(self, checkpoint, mock_proximl):
@@ -340,7 +437,9 @@ class CheckpointTests:
340
437
  assert response.id == "data-id-1"
341
438
 
342
439
  @mark.asyncio
343
- async def test_checkpoint_wait_for_successful(self, checkpoint, mock_proximl):
440
+ async def test_checkpoint_wait_for_successful(
441
+ self, checkpoint, mock_proximl
442
+ ):
344
443
  api_response = {
345
444
  "customer_uuid": "cus-id-1",
346
445
  "checkpoint_uuid": "data-id-1",
@@ -373,7 +472,9 @@ class CheckpointTests:
373
472
  mock_proximl._query.assert_not_called()
374
473
 
375
474
  @mark.asyncio
376
- async def test_checkpoint_wait_for_incorrect_status(self, checkpoint, mock_proximl):
475
+ async def test_checkpoint_wait_for_incorrect_status(
476
+ self, checkpoint, mock_proximl
477
+ ):
377
478
  api_response = None
378
479
  mock_proximl._query = AsyncMock(return_value=api_response)
379
480
  with raises(SpecificationError):
@@ -381,7 +482,9 @@ class CheckpointTests:
381
482
  mock_proximl._query.assert_not_called()
382
483
 
383
484
  @mark.asyncio
384
- async def test_checkpoint_wait_for_with_delay(self, checkpoint, mock_proximl):
485
+ async def test_checkpoint_wait_for_with_delay(
486
+ self, checkpoint, mock_proximl
487
+ ):
385
488
  api_response_initial = dict(
386
489
  checkpoint_uuid="1",
387
490
  name="first one",
@@ -437,7 +540,9 @@ class CheckpointTests:
437
540
  self, checkpoint, mock_proximl
438
541
  ):
439
542
  mock_proximl._query = AsyncMock(
440
- side_effect=ApiError(404, dict(errorMessage="Checkpoint Not Found"))
543
+ side_effect=ApiError(
544
+ 404, dict(errorMessage="Checkpoint Not Found")
545
+ )
441
546
  )
442
547
  await checkpoint.wait_for("archived")
443
548
  mock_proximl._query.assert_called()
@@ -447,8 +552,88 @@ class CheckpointTests:
447
552
  self, checkpoint, mock_proximl
448
553
  ):
449
554
  mock_proximl._query = AsyncMock(
450
- side_effect=ApiError(404, dict(errorMessage="Checkpoint Not Found"))
555
+ side_effect=ApiError(
556
+ 404, dict(errorMessage="Checkpoint Not Found")
557
+ )
451
558
  )
452
559
  with raises(ApiError):
453
560
  await checkpoint.wait_for("ready")
454
561
  mock_proximl._query.assert_called()
562
+
563
+ @mark.asyncio
564
+ async def test_checkpoint_rename(self, checkpoint, mock_proximl):
565
+ api_response = dict(
566
+ checkpoint_uuid="1",
567
+ name="renamed checkpoint",
568
+ project_uuid="proj-id-1",
569
+ status="ready",
570
+ )
571
+ mock_proximl._query = AsyncMock(return_value=api_response)
572
+ result = await checkpoint.rename("renamed checkpoint")
573
+ mock_proximl._query.assert_called_once_with(
574
+ "/checkpoint/1",
575
+ "PATCH",
576
+ dict(project_uuid="proj-id-1"),
577
+ dict(name="renamed checkpoint"),
578
+ )
579
+ assert result == checkpoint
580
+ assert checkpoint.name == "renamed checkpoint"
581
+
582
+ @mark.asyncio
583
+ async def test_checkpoint_export(self, checkpoint, mock_proximl):
584
+ api_response = dict(
585
+ checkpoint_uuid="1",
586
+ name="first one",
587
+ project_uuid="proj-id-1",
588
+ status="exporting",
589
+ )
590
+ mock_proximl._query = AsyncMock(return_value=api_response)
591
+ result = await checkpoint.export("aws", "s3://bucket/path", dict(key="value"))
592
+ mock_proximl._query.assert_called_once_with(
593
+ "/checkpoint/1/export",
594
+ "POST",
595
+ dict(project_uuid="proj-id-1"),
596
+ dict(
597
+ output_type="aws",
598
+ output_uri="s3://bucket/path",
599
+ output_options=dict(key="value"),
600
+ ),
601
+ )
602
+ assert result == checkpoint
603
+ assert checkpoint.status == "exporting"
604
+
605
+ @mark.asyncio
606
+ async def test_checkpoint_export_default_options(self, checkpoint, mock_proximl):
607
+ api_response = dict(
608
+ checkpoint_uuid="1",
609
+ name="first one",
610
+ project_uuid="proj-id-1",
611
+ status="exporting",
612
+ )
613
+ mock_proximl._query = AsyncMock(return_value=api_response)
614
+ result = await checkpoint.export("aws", "s3://bucket/path")
615
+ mock_proximl._query.assert_called_once_with(
616
+ "/checkpoint/1/export",
617
+ "POST",
618
+ dict(project_uuid="proj-id-1"),
619
+ dict(
620
+ output_type="aws",
621
+ output_uri="s3://bucket/path",
622
+ output_options=dict(),
623
+ ),
624
+ )
625
+ assert result == checkpoint
626
+
627
+ @mark.asyncio
628
+ async def test_checkpoint_wait_for_timeout_validation(
629
+ self, checkpoint, mock_proximl
630
+ ):
631
+ with raises(SpecificationError) as exc_info:
632
+ await checkpoint.wait_for("ready", timeout=25 * 60 * 60) # > 24 hours
633
+ assert "timeout" in str(exc_info.value.attribute).lower()
634
+ assert "less than" in str(exc_info.value.message).lower()
635
+
636
+ def test_checkpoint_billed_size_property(self, checkpoint, mock_proximl):
637
+ """Test billed_size property access."""
638
+ checkpoint._billed_size = 50000
639
+ assert checkpoint.billed_size == 50000