trainml 0.5.12__tar.gz → 0.5.14__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. {trainml-0.5.12 → trainml-0.5.14}/PKG-INFO +1 -1
  2. trainml-0.5.14/tests/integration/projects/test_projects_members_integration.py +49 -0
  3. {trainml-0.5.12 → trainml-0.5.14}/tests/integration/test_jobs_integration.py +9 -9
  4. trainml-0.5.14/tests/unit/projects/test_project_members_unit.py +107 -0
  5. {trainml-0.5.12 → trainml-0.5.14}/trainml/__init__.py +1 -1
  6. {trainml-0.5.12 → trainml-0.5.14}/trainml/auth.py +1 -1
  7. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/job/create.py +32 -2
  8. {trainml-0.5.12 → trainml-0.5.14}/trainml/jobs.py +9 -0
  9. {trainml-0.5.12 → trainml-0.5.14}/trainml/projects/data_connectors.py +16 -0
  10. {trainml-0.5.12 → trainml-0.5.14}/trainml/projects/datastores.py +16 -0
  11. trainml-0.5.14/trainml/projects/members.py +98 -0
  12. {trainml-0.5.12 → trainml-0.5.14}/trainml/projects/projects.py +2 -0
  13. {trainml-0.5.12 → trainml-0.5.14}/trainml/projects/services.py +16 -0
  14. {trainml-0.5.12 → trainml-0.5.14}/trainml/trainml.py +37 -20
  15. {trainml-0.5.12 → trainml-0.5.14}/trainml.egg-info/PKG-INFO +1 -1
  16. {trainml-0.5.12 → trainml-0.5.14}/trainml.egg-info/SOURCES.txt +3 -0
  17. {trainml-0.5.12 → trainml-0.5.14}/LICENSE +0 -0
  18. {trainml-0.5.12 → trainml-0.5.14}/README.md +0 -0
  19. {trainml-0.5.12 → trainml-0.5.14}/examples/__init__.py +0 -0
  20. {trainml-0.5.12 → trainml-0.5.14}/examples/create_dataset_and_training_job.py +0 -0
  21. {trainml-0.5.12 → trainml-0.5.14}/examples/local_storage.py +0 -0
  22. {trainml-0.5.12 → trainml-0.5.14}/examples/training_inference_pipeline.py +0 -0
  23. {trainml-0.5.12 → trainml-0.5.14}/pyproject.toml +0 -0
  24. {trainml-0.5.12 → trainml-0.5.14}/setup.cfg +0 -0
  25. {trainml-0.5.12 → trainml-0.5.14}/setup.py +0 -0
  26. {trainml-0.5.12 → trainml-0.5.14}/tests/integration/__init__.py +0 -0
  27. {trainml-0.5.12 → trainml-0.5.14}/tests/integration/cloudbender/__init__.py +0 -0
  28. {trainml-0.5.12 → trainml-0.5.14}/tests/integration/cloudbender/test_providers_integration.py +0 -0
  29. {trainml-0.5.12 → trainml-0.5.14}/tests/integration/conftest.py +0 -0
  30. {trainml-0.5.12 → trainml-0.5.14}/tests/integration/projects/__init__.py +0 -0
  31. {trainml-0.5.12 → trainml-0.5.14}/tests/integration/projects/conftest.py +0 -0
  32. {trainml-0.5.12 → trainml-0.5.14}/tests/integration/projects/test_projects_credentials_integration.py +0 -0
  33. {trainml-0.5.12 → trainml-0.5.14}/tests/integration/projects/test_projects_data_connectors_integration.py +0 -0
  34. {trainml-0.5.12 → trainml-0.5.14}/tests/integration/projects/test_projects_datastores_integration.py +0 -0
  35. {trainml-0.5.12 → trainml-0.5.14}/tests/integration/projects/test_projects_integration.py +0 -0
  36. {trainml-0.5.12 → trainml-0.5.14}/tests/integration/projects/test_projects_secrets_integration.py +0 -0
  37. {trainml-0.5.12 → trainml-0.5.14}/tests/integration/projects/test_projects_services_integration.py +0 -0
  38. {trainml-0.5.12 → trainml-0.5.14}/tests/integration/test_checkpoints_integration.py +0 -0
  39. {trainml-0.5.12 → trainml-0.5.14}/tests/integration/test_datasets_integration.py +0 -0
  40. {trainml-0.5.12 → trainml-0.5.14}/tests/integration/test_environments_integration.py +0 -0
  41. {trainml-0.5.12 → trainml-0.5.14}/tests/integration/test_gpu_types_integration.py +0 -0
  42. {trainml-0.5.12 → trainml-0.5.14}/tests/integration/test_models_integration.py +0 -0
  43. {trainml-0.5.12 → trainml-0.5.14}/tests/integration/test_volumes_integration.py +0 -0
  44. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/__init__.py +0 -0
  45. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cli/__init__.py +0 -0
  46. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cli/cloudbender/__init__.py +0 -0
  47. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cli/cloudbender/test_cli_datastore_unit.py +0 -0
  48. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cli/cloudbender/test_cli_device_unit.py +0 -0
  49. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cli/cloudbender/test_cli_node_unit.py +0 -0
  50. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cli/cloudbender/test_cli_provider_unit.py +0 -0
  51. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cli/cloudbender/test_cli_region_unit.py +0 -0
  52. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cli/cloudbender/test_cli_service_unit.py +0 -0
  53. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cli/conftest.py +0 -0
  54. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cli/projects/__init__.py +0 -0
  55. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cli/projects/test_cli_project_credential_unit.py +0 -0
  56. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cli/projects/test_cli_project_data_connector_unit.py +0 -0
  57. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cli/projects/test_cli_project_datastore_unit.py +0 -0
  58. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cli/projects/test_cli_project_secret_unit.py +0 -0
  59. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cli/projects/test_cli_project_service_unit.py +0 -0
  60. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cli/projects/test_cli_project_unit.py +0 -0
  61. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cli/test_cli_checkpoint_unit.py +0 -0
  62. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cli/test_cli_datasets_unit.py +0 -0
  63. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cli/test_cli_environment_unit.py +0 -0
  64. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cli/test_cli_gpu_unit.py +0 -0
  65. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cli/test_cli_job_unit.py +0 -0
  66. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cli/test_cli_model_unit.py +0 -0
  67. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cli/test_cli_volume_unit.py +0 -0
  68. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cloudbender/__init__.py +0 -0
  69. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cloudbender/test_data_connectors_unit.py +0 -0
  70. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cloudbender/test_datastores_unit.py +0 -0
  71. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cloudbender/test_device_configs_unit.py +0 -0
  72. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cloudbender/test_devices_unit.py +0 -0
  73. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cloudbender/test_nodes_unit.py +0 -0
  74. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cloudbender/test_providers_unit.py +0 -0
  75. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cloudbender/test_regions_unit.py +0 -0
  76. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/cloudbender/test_services_unit.py +0 -0
  77. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/conftest.py +0 -0
  78. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/projects/__init__.py +0 -0
  79. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/projects/test_project_credentials_unit.py +0 -0
  80. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/projects/test_project_data_connectors_unit.py +0 -0
  81. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/projects/test_project_datastores_unit.py +0 -0
  82. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/projects/test_project_secrets_unit.py +0 -0
  83. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/projects/test_project_services_unit.py +0 -0
  84. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/projects/test_projects_unit.py +0 -0
  85. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/test_auth_unit.py +0 -0
  86. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/test_checkpoints_unit.py +0 -0
  87. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/test_connections_unit.py +0 -0
  88. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/test_datasets_unit.py +0 -0
  89. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/test_environments_unit.py +0 -0
  90. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/test_exceptions.py +0 -0
  91. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/test_gpu_types_unit.py +0 -0
  92. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/test_jobs_unit.py +0 -0
  93. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/test_models_unit.py +0 -0
  94. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/test_trainml_unit.py +0 -0
  95. {trainml-0.5.12 → trainml-0.5.14}/tests/unit/test_volumes_unit.py +0 -0
  96. {trainml-0.5.12 → trainml-0.5.14}/trainml/__main__.py +0 -0
  97. {trainml-0.5.12 → trainml-0.5.14}/trainml/checkpoints.py +0 -0
  98. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/__init__.py +0 -0
  99. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/checkpoint.py +0 -0
  100. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/cloudbender/__init__.py +0 -0
  101. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/cloudbender/data_connector.py +0 -0
  102. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/cloudbender/datastore.py +0 -0
  103. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/cloudbender/device.py +0 -0
  104. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/cloudbender/node.py +0 -0
  105. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/cloudbender/provider.py +0 -0
  106. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/cloudbender/region.py +0 -0
  107. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/cloudbender/service.py +0 -0
  108. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/connection.py +0 -0
  109. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/dataset.py +0 -0
  110. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/environment.py +0 -0
  111. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/gpu.py +0 -0
  112. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/job/__init__.py +0 -0
  113. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/model.py +0 -0
  114. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/project/__init__.py +0 -0
  115. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/project/credential.py +0 -0
  116. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/project/data_connector.py +0 -0
  117. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/project/datastore.py +0 -0
  118. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/project/secret.py +0 -0
  119. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/project/service.py +0 -0
  120. {trainml-0.5.12 → trainml-0.5.14}/trainml/cli/volume.py +0 -0
  121. {trainml-0.5.12 → trainml-0.5.14}/trainml/cloudbender/__init__.py +0 -0
  122. {trainml-0.5.12 → trainml-0.5.14}/trainml/cloudbender/cloudbender.py +0 -0
  123. {trainml-0.5.12 → trainml-0.5.14}/trainml/cloudbender/data_connectors.py +0 -0
  124. {trainml-0.5.12 → trainml-0.5.14}/trainml/cloudbender/datastores.py +0 -0
  125. {trainml-0.5.12 → trainml-0.5.14}/trainml/cloudbender/device_configs.py +0 -0
  126. {trainml-0.5.12 → trainml-0.5.14}/trainml/cloudbender/devices.py +0 -0
  127. {trainml-0.5.12 → trainml-0.5.14}/trainml/cloudbender/nodes.py +0 -0
  128. {trainml-0.5.12 → trainml-0.5.14}/trainml/cloudbender/providers.py +0 -0
  129. {trainml-0.5.12 → trainml-0.5.14}/trainml/cloudbender/regions.py +0 -0
  130. {trainml-0.5.12 → trainml-0.5.14}/trainml/cloudbender/services.py +0 -0
  131. {trainml-0.5.12 → trainml-0.5.14}/trainml/connections.py +0 -0
  132. {trainml-0.5.12 → trainml-0.5.14}/trainml/datasets.py +0 -0
  133. {trainml-0.5.12 → trainml-0.5.14}/trainml/environments.py +0 -0
  134. {trainml-0.5.12 → trainml-0.5.14}/trainml/exceptions.py +0 -0
  135. {trainml-0.5.12 → trainml-0.5.14}/trainml/gpu_types.py +0 -0
  136. {trainml-0.5.12 → trainml-0.5.14}/trainml/models.py +0 -0
  137. {trainml-0.5.12 → trainml-0.5.14}/trainml/projects/__init__.py +0 -0
  138. {trainml-0.5.12 → trainml-0.5.14}/trainml/projects/credentials.py +0 -0
  139. {trainml-0.5.12 → trainml-0.5.14}/trainml/projects/secrets.py +0 -0
  140. {trainml-0.5.12 → trainml-0.5.14}/trainml/volumes.py +0 -0
  141. {trainml-0.5.12 → trainml-0.5.14}/trainml.egg-info/dependency_links.txt +0 -0
  142. {trainml-0.5.12 → trainml-0.5.14}/trainml.egg-info/entry_points.txt +0 -0
  143. {trainml-0.5.12 → trainml-0.5.14}/trainml.egg-info/requires.txt +0 -0
  144. {trainml-0.5.12 → trainml-0.5.14}/trainml.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: trainml
3
- Version: 0.5.12
3
+ Version: 0.5.14
4
4
  Summary: trainML client SDK and command line utilities
5
5
  Home-page: https://github.com/trainML/trainml-cli
6
6
  Author: trainML
@@ -0,0 +1,49 @@
1
+ import re
2
+ import sys
3
+ import asyncio
4
+ from pytest import mark, fixture
5
+
6
+ pytestmark = [mark.sdk, mark.integration, mark.projects]
7
+
8
+
9
+ @mark.create
10
+ @mark.asyncio
11
+ class ProjectMembersTests:
12
+ @fixture(scope="class")
13
+ async def project_member(self, project):
14
+ member = await project.members.add("test.account@proximl.ai","read","read","read","read","read")
15
+ yield member
16
+ await project.members.remove("test.account@proximl.ai")
17
+
18
+ async def test_list_project_members(self, project):
19
+ members = await project.members.list()
20
+ assert len(members) > 0
21
+
22
+ async def test_project_member_properties(self, project, project_member):
23
+ assert isinstance(project_member.id, str)
24
+ assert isinstance(project_member.email, str)
25
+ assert isinstance(project_member.project_uuid, str)
26
+ assert isinstance(project_member.owner, bool)
27
+ assert isinstance(project_member.job, str)
28
+ assert isinstance(project_member.dataset, str)
29
+ assert isinstance(project_member.model, str)
30
+ assert isinstance(project_member.checkpoint, str)
31
+ assert isinstance(project_member.volume, str)
32
+ assert project.id == project_member.project_uuid
33
+ assert project_member.id == "test.account@proximl.ai"
34
+
35
+ async def test_project_member_str(self, project_member):
36
+ string = str(project_member)
37
+ regex = r"^{.*\"email\": \"" + project_member.email + r"\".*}$"
38
+ assert isinstance(string, str)
39
+ assert re.match(regex, string)
40
+
41
+ async def test_project_member_repr(self, project_member):
42
+ string = repr(project_member)
43
+ regex = (
44
+ r"^ProjectMember\( trainml , \*\*{.*'email': '"
45
+ + project_member.email
46
+ + r"'.*}\)$"
47
+ )
48
+ assert isinstance(string, str)
49
+ assert re.match(regex, string)
@@ -175,7 +175,7 @@ class JobAPIResourceValidationTests:
175
175
  disk_size=10,
176
176
  )
177
177
  assert (
178
- "Invalid Request - None (CPU Only) may be not be combined with other GPU Types"
178
+ "Invalid Request - CPU Only may be not be combined with other GPU Types"
179
179
  in error.value.message
180
180
  )
181
181
 
@@ -423,11 +423,11 @@ class JobIOTests:
423
423
  disk_size=10,
424
424
  workers=["python $ML_MODEL_PATH/tensorflow/main.py"],
425
425
  environment=dict(
426
- type="DEEPLEARNING_PY310",
426
+ type="DEEPLEARNING_PY312",
427
427
  env=[
428
428
  dict(
429
429
  key="CHECKPOINT_FILE",
430
- value="model.ckpt-0050",
430
+ value="model.ckpt-0050.weights.h5",
431
431
  )
432
432
  ],
433
433
  ),
@@ -469,7 +469,7 @@ class JobIOTests:
469
469
  sys.stderr.write(captured.err)
470
470
  assert "Epoch 1/2" in captured.out
471
471
  assert "Epoch 2/2" in captured.out
472
- assert "adding: model.ckpt-0001.data-00000-of-00001" in captured.out
472
+ assert "adding: model.ckpt-0001" in captured.out
473
473
  assert "Send complete" in captured.out
474
474
 
475
475
  async def test_job_model_input_and_output(self, trainml, capsys):
@@ -558,7 +558,7 @@ class JobTypeTests:
558
558
  assert job.url
559
559
  assert extract_domain_suffix(urlparse(job.url).hostname) == "proximl.cloud"
560
560
  tries = 0
561
- await asyncio.sleep(30)
561
+ await asyncio.sleep(180) ## downloading weights can be slow
562
562
  async with aiohttp.ClientSession() as session:
563
563
  retry = True
564
564
  while retry:
@@ -640,7 +640,7 @@ class JobTypeTests:
640
640
  assert "Epoch 2/2" in captured.out
641
641
  assert "Uploading s3://trainml-example/output/resnet_cifar10" in captured.out
642
642
  assert (
643
- "upload: ./model.ckpt-0002.data-00000-of-00001 to s3://trainml-example/output/resnet_cifar10/model.ckpt-0002.data-00000-of-00001"
643
+ "upload: ./model.ckpt-0002.weights.h5 to s3://trainml-example/output/resnet_cifar10/model.ckpt-0002.weights.h5"
644
644
  in captured.out
645
645
  )
646
646
  assert "Upload complete" in captured.out
@@ -713,9 +713,9 @@ class JobFeatureTests:
713
713
  sys.stderr.write(captured.err)
714
714
  upload_contents = os.listdir(temp_dir.name)
715
715
  temp_dir.cleanup()
716
- assert len(upload_contents) > 4
716
+ assert len(upload_contents) >= 3
717
717
  assert any(
718
- "model.ckpt-0002.data-00000-of-00001" in content
718
+ "model.ckpt-0002" in content
719
719
  for content in upload_contents
720
720
  )
721
721
 
@@ -724,5 +724,5 @@ class JobFeatureTests:
724
724
  sys.stderr.write(captured.err)
725
725
  assert "Epoch 1/2" in captured.out
726
726
  assert "Epoch 2/2" in captured.out
727
- assert "Number of regular files transferred: 7" in captured.out
727
+ assert "Number of regular files transferred: 4" in captured.out
728
728
  assert "Send complete" in captured.out
@@ -0,0 +1,107 @@
1
+ import re
2
+ import json
3
+ import logging
4
+ from unittest.mock import AsyncMock, patch
5
+ from pytest import mark, fixture, raises
6
+ from aiohttp import WSMessage, WSMsgType
7
+
8
+ import trainml.projects.members as specimen
9
+ from trainml.exceptions import (
10
+ ApiError,
11
+ SpecificationError,
12
+ TrainMLException,
13
+ )
14
+
15
+ pytestmark = [mark.sdk, mark.unit, mark.projects]
16
+
17
+
18
+ @fixture
19
+ def project_members(mock_trainml):
20
+ yield specimen.ProjectMembers(mock_trainml, project_id="1")
21
+
22
+
23
+ @fixture
24
+ def project_member(mock_trainml):
25
+ yield specimen.ProjectMember(
26
+ mock_trainml,
27
+ id="owner@gmail.com",
28
+ email="owner@gmail.com",
29
+ project_uuid="proj-id-1",
30
+ owner= True,
31
+ job= "all",
32
+ dataset= "all",
33
+ model= "all",
34
+ checkpoint="all",
35
+ volume= "all"
36
+ )
37
+
38
+
39
+ class ProjectMembersTests:
40
+ @mark.asyncio
41
+ async def test_project_members_list(self, project_members, mock_trainml):
42
+ api_response = [
43
+ {
44
+ "project_uuid": "proj-id-1",
45
+ "email": "owner@gmail.com",
46
+ "createdAt": "2024-09-04T00:42:39.529Z",
47
+ "updatedAt": "2024-09-04T00:42:39.529Z",
48
+ "owner": True,
49
+ "job": "all",
50
+ "dataset": "all",
51
+ "model": "all",
52
+ "checkpoint": "all",
53
+ "volume": "all"
54
+ },
55
+ {
56
+ "project_uuid": "proj-id-1",
57
+ "email": "non-owner@gmail.com",
58
+ "createdAt": "2024-09-04T00:42:39.529Z",
59
+ "updatedAt": "2024-09-04T00:42:39.529Z",
60
+ "owner": False,
61
+ "job": "all",
62
+ "dataset": "all",
63
+ "model": "all",
64
+ "checkpoint": "read",
65
+ "volume": "read"
66
+ },
67
+ ]
68
+ mock_trainml._query = AsyncMock(return_value=api_response)
69
+ resp = await project_members.list()
70
+ mock_trainml._query.assert_called_once_with(
71
+ "/project/1/access", "GET", dict()
72
+ )
73
+ assert len(resp) == 2
74
+
75
+
76
+ class ProjectMemberTests:
77
+ def test_project_member_properties(self, project_member):
78
+ assert isinstance(project_member.id, str)
79
+ assert isinstance(project_member.email, str)
80
+ assert isinstance(project_member.project_uuid, str)
81
+ assert isinstance(project_member.owner, bool)
82
+ assert isinstance(project_member.job, str)
83
+ assert isinstance(project_member.dataset, str)
84
+ assert isinstance(project_member.model, str)
85
+ assert isinstance(project_member.checkpoint, str)
86
+ assert isinstance(project_member.volume, str)
87
+
88
+ def test_project_member_str(self, project_member):
89
+ string = str(project_member)
90
+ regex = r"^{.*\"id\": \"" + project_member.id + r"\".*}$"
91
+ assert isinstance(string, str)
92
+ assert re.match(regex, string)
93
+
94
+ def test_project_member_repr(self, project_member):
95
+ string = repr(project_member)
96
+ regex = (
97
+ r"^ProjectMember\( trainml , \*\*{.*'id': '"
98
+ + project_member.id
99
+ + r"'.*}\)$"
100
+ )
101
+ assert isinstance(string, str)
102
+ assert re.match(regex, string)
103
+
104
+ def test_project_member_bool(self, project_member, mock_trainml):
105
+ empty_project_member = specimen.ProjectMember(mock_trainml)
106
+ assert bool(project_member)
107
+ assert not bool(empty_project_member)
@@ -13,5 +13,5 @@ logging.basicConfig(
13
13
  logger = logging.getLogger(__name__)
14
14
 
15
15
 
16
- __version__ = "0.5.12"
16
+ __version__ = "0.5.14"
17
17
  __all__ = "TrainML"
@@ -508,7 +508,7 @@ class AWSSRP(object):
508
508
 
509
509
 
510
510
  class Auth(object):
511
- def __init__(self, config_dir, domain_suffix="trainml.ai", **kwargs):
511
+ def __init__(self, config_dir, domain_suffix="proximl.ai", **kwargs):
512
512
  try:
513
513
  with open(f"{config_dir}/environment.json", "r") as file:
514
514
  env_str = file.read().replace("\n", "")
@@ -180,6 +180,12 @@ def create(config):
180
180
  help="Third Party Credentials to add to the job environment",
181
181
  multiple=True,
182
182
  )
183
+ @click.option(
184
+ "--secret",
185
+ type=click.STRING,
186
+ help="Project secrets to add to the job environment",
187
+ multiple=True,
188
+ )
183
189
  @click.option(
184
190
  "--git-uri",
185
191
  type=click.STRING,
@@ -222,6 +228,7 @@ def notebook(
222
228
  custom_image,
223
229
  env,
224
230
  credential,
231
+ secret,
225
232
  apt_packages,
226
233
  pip_packages,
227
234
  conda_packages,
@@ -254,7 +261,7 @@ def notebook(
254
261
  data=dict(datasets=datasets),
255
262
  model=dict(checkpoints=checkpoints),
256
263
  environment=dict(
257
- credentials=[k for k in credential],
264
+ credentials=[k for k in credential], secrets=[s for s in secret]
258
265
  ),
259
266
  )
260
267
 
@@ -508,6 +515,12 @@ def notebook(
508
515
  help="Third Party Credentials to add to the job environment",
509
516
  multiple=True,
510
517
  )
518
+ @click.option(
519
+ "--secret",
520
+ type=click.STRING,
521
+ help="Project secrets to add to the job environment",
522
+ multiple=True,
523
+ )
511
524
  @click.option(
512
525
  "--apt-packages",
513
526
  type=click.STRING,
@@ -564,6 +577,7 @@ def training(
564
577
  custom_image,
565
578
  env,
566
579
  credential,
580
+ secret,
567
581
  apt_packages,
568
582
  pip_packages,
569
583
  conda_packages,
@@ -596,7 +610,7 @@ def training(
596
610
  data=dict(datasets=datasets),
597
611
  model=dict(checkpoints=checkpoints),
598
612
  environment=dict(
599
- credentials=[k for k in credential],
613
+ credentials=[k for k in credential], secrets=[s for s in secret]
600
614
  ),
601
615
  )
602
616
 
@@ -852,6 +866,12 @@ def training(
852
866
  help="Third Party Credentials to add to the job environment",
853
867
  multiple=True,
854
868
  )
869
+ @click.option(
870
+ "--secret",
871
+ type=click.STRING,
872
+ help="Project secrets to add to the job environment",
873
+ multiple=True,
874
+ )
855
875
  @click.option(
856
876
  "--apt-packages",
857
877
  type=click.STRING,
@@ -908,6 +928,7 @@ def inference(
908
928
  custom_image,
909
929
  env,
910
930
  credential,
931
+ secret,
911
932
  apt_packages,
912
933
  pip_packages,
913
934
  conda_packages,
@@ -934,6 +955,7 @@ def inference(
934
955
  model=dict(checkpoints=checkpoints),
935
956
  environment=dict(
936
957
  credentials=[k for k in credential],
958
+ secrets=[s for s in secret],
937
959
  ),
938
960
  )
939
961
 
@@ -1171,6 +1193,12 @@ def from_json(config, attach, connect, file):
1171
1193
  help="Third Party Credentials to add to the job environment",
1172
1194
  multiple=True,
1173
1195
  )
1196
+ @click.option(
1197
+ "--secret",
1198
+ type=click.STRING,
1199
+ help="Project secrets to add to the job environment",
1200
+ multiple=True,
1201
+ )
1174
1202
  @click.option(
1175
1203
  "--apt-packages",
1176
1204
  type=click.STRING,
@@ -1231,6 +1259,7 @@ def endpoint(
1231
1259
  custom_image,
1232
1260
  env,
1233
1261
  credential,
1262
+ secret,
1234
1263
  apt_packages,
1235
1264
  pip_packages,
1236
1265
  conda_packages,
@@ -1258,6 +1287,7 @@ def endpoint(
1258
1287
  model=dict(checkpoints=checkpoints),
1259
1288
  environment=dict(
1260
1289
  credentials=[k for k in credential],
1290
+ secrets=[s for s in secret],
1261
1291
  ),
1262
1292
  )
1263
1293
 
@@ -530,6 +530,15 @@ class Job:
530
530
  "waiting for data/model download",
531
531
  ]
532
532
  )
533
+ or (
534
+ status
535
+ == "running" ## this status could be too short for polling could miss it
536
+ and self.status
537
+ in [
538
+ "uploading",
539
+ "finished"
540
+ ]
541
+ )
533
542
  ):
534
543
  return self
535
544
  elif self.status == "failed":
@@ -7,6 +7,12 @@ class ProjectDataConnectors(object):
7
7
  self.trainml = trainml
8
8
  self.project_id = project_id
9
9
 
10
+ async def get(self, id, **kwargs):
11
+ resp = await self.trainml._query(
12
+ f"/project/{self.project_id}/data_connectors/{id}", "GET", kwargs
13
+ )
14
+ return ProjectDataConnector(self.trainml, **resp)
15
+
10
16
  async def list(self, **kwargs):
11
17
  resp = await self.trainml._query(
12
18
  f"/project/{self.project_id}/data_connectors", "GET", kwargs
@@ -61,3 +67,13 @@ class ProjectDataConnector:
61
67
 
62
68
  def __bool__(self):
63
69
  return bool(self._id)
70
+
71
+ async def enable(self):
72
+ await self.trainml._query(
73
+ f"/project/{self._project_uuid}/data_connectors/{self._id}/enable", "PATCH"
74
+ )
75
+
76
+ async def disable(self):
77
+ await self.trainml._query(
78
+ f"/project/{self._project_uuid}/data_connectors/{self._id}/disable", "PATCH"
79
+ )
@@ -7,6 +7,12 @@ class ProjectDatastores(object):
7
7
  self.trainml = trainml
8
8
  self.project_id = project_id
9
9
 
10
+ async def get(self, id, **kwargs):
11
+ resp = await self.trainml._query(
12
+ f"/project/{self.project_id}/datastores/{id}", "GET", kwargs
13
+ )
14
+ return ProjectDatastore(self.trainml, **resp)
15
+
10
16
  async def list(self, **kwargs):
11
17
  resp = await self.trainml._query(
12
18
  f"/project/{self.project_id}/datastores", "GET", kwargs
@@ -56,3 +62,13 @@ class ProjectDatastore:
56
62
 
57
63
  def __bool__(self):
58
64
  return bool(self._id)
65
+
66
+ async def enable(self):
67
+ await self.trainml._query(
68
+ f"/project/{self._project_uuid}/datastores/{self._id}/enable", "PATCH"
69
+ )
70
+
71
+ async def disable(self):
72
+ await self.trainml._query(
73
+ f"/project/{self._project_uuid}/datastores/{self._id}/disable", "PATCH"
74
+ )
@@ -0,0 +1,98 @@
1
+ import json
2
+ import logging
3
+ from typing import Literal
4
+
5
+ class ProjectMembers(object):
6
+ def __init__(self, trainml, project_id):
7
+ self.trainml = trainml
8
+ self.project_id = project_id
9
+
10
+ async def list(self, **kwargs):
11
+ resp = await self.trainml._query(
12
+ f"/project/{self.project_id}/access", "GET", kwargs
13
+ )
14
+ members = [ProjectMember(self.trainml, **member) for member in resp]
15
+ return members
16
+
17
+ async def add(self, email: str, job: Literal["all", "read"], dataset: Literal["all", "read"], model: Literal["all", "read"], checkpoint: Literal["all", "read"], volume: Literal["all", "read"], **kwargs):
18
+ data = dict(
19
+ email=email,
20
+ job=job,
21
+ dataset=dataset,
22
+ model=model,
23
+ checkpoint=checkpoint,
24
+ volume=volume,
25
+ )
26
+ payload = {k: v for k, v in data.items() if v is not None}
27
+ resp = await self.trainml._query(
28
+ f"/project/{self.project_id}/access", "POST",kwargs, payload)
29
+ member = ProjectMember(self.trainml, **resp)
30
+ logging.info(f"Added Project Member {email} to project {self.project_id}")
31
+ return member
32
+
33
+
34
+ async def remove(self, email, **kwargs):
35
+ await self.trainml._query(
36
+ f"/project/{self.project_id}/access", "DELETE", dict(**kwargs, email=email)
37
+ )
38
+
39
+
40
+ class ProjectMember:
41
+ def __init__(self, trainml, **kwargs):
42
+ self.trainml = trainml
43
+ self._entity = kwargs
44
+ self._id = self._entity.get("email")
45
+ self._project_uuid = self._entity.get("project_uuid")
46
+ self._owner = self._entity.get("owner")
47
+ self._job = self._entity.get("job")
48
+ self._dataset = self._entity.get("dataset")
49
+ self._model = self._entity.get("model")
50
+ self._checkpoint = self._entity.get("checkpoint")
51
+ self._volume = self._entity.get("volume")
52
+
53
+ @property
54
+ def id(self) -> str:
55
+ return self._id
56
+
57
+ @property
58
+ def project_uuid(self) -> str:
59
+ return self._project_uuid
60
+
61
+ @property
62
+ def email(self) -> str:
63
+ return self._id
64
+
65
+ @property
66
+ def owner(self) -> bool:
67
+ return self._owner
68
+
69
+ @property
70
+ def job(self) -> str:
71
+ return self._job
72
+
73
+ @property
74
+ def dataset(self) -> str:
75
+ return self._dataset
76
+
77
+ @property
78
+ def model(self) -> str:
79
+ return self._model
80
+
81
+ @property
82
+ def checkpoint(self) -> str:
83
+ return self._checkpoint
84
+
85
+ @property
86
+ def volume(self) -> str:
87
+ return self._volume
88
+
89
+ def __str__(self):
90
+ return json.dumps({k: v for k, v in self._entity.items()})
91
+
92
+ def __repr__(self):
93
+ return f"ProjectMember( trainml , **{self._entity.__repr__()})"
94
+
95
+ def __bool__(self):
96
+ return bool(self._id)
97
+
98
+
@@ -5,6 +5,7 @@ from .data_connectors import ProjectDataConnectors
5
5
  from .services import ProjectServices
6
6
  from .credentials import ProjectCredentials
7
7
  from .secrets import ProjectSecrets
8
+ from .members import ProjectMembers
8
9
 
9
10
 
10
11
  class Projects(object):
@@ -56,6 +57,7 @@ class Project:
56
57
  self.services = ProjectServices(self.trainml, self._id)
57
58
  self.credentials = ProjectCredentials(self.trainml, self._id)
58
59
  self.secrets = ProjectSecrets(self.trainml, self._id)
60
+ self.members = ProjectMembers(self.trainml, self._id)
59
61
 
60
62
  @property
61
63
  def id(self) -> str:
@@ -7,6 +7,12 @@ class ProjectServices(object):
7
7
  self.trainml = trainml
8
8
  self.project_id = project_id
9
9
 
10
+ async def get(self, id, **kwargs):
11
+ resp = await self.trainml._query(
12
+ f"/project/{self.project_id}/services/{id}", "GET", kwargs
13
+ )
14
+ return ProjectService(self.trainml, **resp)
15
+
10
16
  async def list(self, **kwargs):
11
17
  resp = await self.trainml._query(
12
18
  f"/project/{self.project_id}/services", "GET", kwargs
@@ -61,3 +67,13 @@ class ProjectService:
61
67
 
62
68
  def __bool__(self):
63
69
  return bool(self._id)
70
+
71
+ async def enable(self):
72
+ await self.trainml._query(
73
+ f"/project/{self._project_uuid}/services/{self._id}/enable", "PATCH"
74
+ )
75
+
76
+ async def disable(self):
77
+ await self.trainml._query(
78
+ f"/project/{self._project_uuid}/services/{self._id}/disable", "PATCH"
79
+ )
@@ -4,6 +4,7 @@ import asyncio
4
4
  import aiohttp
5
5
  import logging
6
6
  import traceback
7
+ import random
7
8
  from importlib.metadata import version
8
9
 
9
10
  from trainml.auth import Auth
@@ -48,7 +49,7 @@ class TrainML(object):
48
49
  kwargs.get("domain_suffix")
49
50
  or os.environ.get("TRAINML_DOMAIN_SUFFIX")
50
51
  or env.get("domain_suffix")
51
- or "trainml.ai"
52
+ or "proximl.ai"
52
53
  )
53
54
  self.auth = Auth(
54
55
  config_dir=CONFIG_DIR,
@@ -91,7 +92,7 @@ class TrainML(object):
91
92
  def project(self) -> str:
92
93
  return self.active_project
93
94
 
94
- async def _query(self, path, method, params=None, data=None, headers=None):
95
+ async def _query(self, path, method, params=None, data=None, headers=None,max_retries=3, backoff_factor=0.5):
95
96
  try:
96
97
  tokens = self.auth.get_tokens()
97
98
  except TrainMLException as e:
@@ -142,24 +143,40 @@ class TrainML(object):
142
143
  logging.debug(
143
144
  f"Request - Url: {url}, Method: {method}, Params: {params}, Body: {data}, Headers: {headers}"
144
145
  )
145
- async with aiohttp.ClientSession() as session:
146
- async with session.request(
147
- method,
148
- url,
149
- data=json.dumps(data),
150
- headers=headers,
151
- params=params,
152
- ) as resp:
153
- if (resp.status // 100) in [4, 5]:
154
- what = await resp.read()
155
- content_type = resp.headers.get("content-type", "")
156
- resp.close()
157
- if content_type == "application/json":
158
- raise ApiError(resp.status, json.loads(what.decode("utf8")))
159
- else:
160
- raise ApiError(resp.status, {"message": what.decode("utf8")})
161
- results = await resp.json()
162
- return results
146
+ for attempt in range(max_retries):
147
+ try:
148
+ async with aiohttp.ClientSession() as session:
149
+ async with session.request(
150
+ method,
151
+ url,
152
+ data=json.dumps(data),
153
+ headers=headers,
154
+ params=params,
155
+ ) as resp:
156
+ if (resp.status // 100) in [4, 5]:
157
+ if resp.status == 502 and attempt < max_retries - 1:
158
+ wait_time = (2 ** attempt) * backoff_factor * (random.random() + 0.5)
159
+ await asyncio.sleep(wait_time)
160
+ continue
161
+ else:
162
+ what = await resp.read()
163
+ content_type = resp.headers.get("content-type", "")
164
+ resp.close()
165
+ if content_type == "application/json":
166
+ raise ApiError(resp.status, json.loads(what.decode("utf8")))
167
+ else:
168
+ raise ApiError(resp.status, {"message": what.decode("utf8")})
169
+ results = await resp.json()
170
+ return results
171
+ except aiohttp.ClientResponseError as e:
172
+ if e.status == 502 and attempt < max_retries - 1:
173
+ wait_time = (2 ** attempt) * backoff_factor * (random.random() + 0.5)
174
+ await asyncio.sleep(wait_time)
175
+ continue
176
+ else:
177
+ raise ApiError(e.status, f"Error {e.message}")
178
+
179
+ raise TrainMLException("Unexpected API failure")
163
180
 
164
181
  async def _ws_subscribe(self, entity, project_uuid, id, msg_handler):
165
182
  headers = {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: trainml
3
- Version: 0.5.12
3
+ Version: 0.5.14
4
4
  Summary: trainML client SDK and command line utilities
5
5
  Home-page: https://github.com/trainML/trainml-cli
6
6
  Author: trainML
@@ -23,6 +23,7 @@ tests/integration/projects/test_projects_credentials_integration.py
23
23
  tests/integration/projects/test_projects_data_connectors_integration.py
24
24
  tests/integration/projects/test_projects_datastores_integration.py
25
25
  tests/integration/projects/test_projects_integration.py
26
+ tests/integration/projects/test_projects_members_integration.py
26
27
  tests/integration/projects/test_projects_secrets_integration.py
27
28
  tests/integration/projects/test_projects_services_integration.py
28
29
  tests/unit/__init__.py
@@ -74,6 +75,7 @@ tests/unit/projects/__init__.py
74
75
  tests/unit/projects/test_project_credentials_unit.py
75
76
  tests/unit/projects/test_project_data_connectors_unit.py
76
77
  tests/unit/projects/test_project_datastores_unit.py
78
+ tests/unit/projects/test_project_members_unit.py
77
79
  tests/unit/projects/test_project_secrets_unit.py
78
80
  tests/unit/projects/test_project_services_unit.py
79
81
  tests/unit/projects/test_projects_unit.py
@@ -134,6 +136,7 @@ trainml/projects/__init__.py
134
136
  trainml/projects/credentials.py
135
137
  trainml/projects/data_connectors.py
136
138
  trainml/projects/datastores.py
139
+ trainml/projects/members.py
137
140
  trainml/projects/projects.py
138
141
  trainml/projects/secrets.py
139
142
  trainml/projects/services.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes